From 447b4c9a2e99a37c0d17e01e79d786f2c11590f5 Mon Sep 17 00:00:00 2001 From: Jordan Bracco Date: Sat, 16 May 2020 12:17:40 +0200 Subject: Format and prepare for release --- .credo.exs | 164 ++++++++++++++++++++++++++++++++++++++ .gitlab-ci.yml | 40 ++++++++++ CHANGELOG.md | 9 +++ LICENSE | 165 +++++++++++++++++++++++++++++++++++++++ README.md | 2 +- lib/concurrent_limiter.ex | 69 ++++++++++------ mix.exs | 23 +++++- mix.lock | 3 + test/concurrent_limiter_test.exs | 53 ++++++------- test/samples/multi_limiter.exs | 69 ++++++++-------- test/samples/update_counter.exs | 33 ++++---- 11 files changed, 527 insertions(+), 103 deletions(-) create mode 100644 .credo.exs create mode 100644 .gitlab-ci.yml create mode 100644 CHANGELOG.md create mode 100644 LICENSE diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..ec6da94 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,164 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any exec using `mix credo -C `. If no exec name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: ["lib/", "src/", "test/", "web/", "apps/"], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/", ~r"test/samples/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: true, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + # TODO: enable by default in Credo 1.1 + {Credo.Check.Readability.UnnecessaryAliasExpansion, false}, + {Credo.Check.Readability.VariableNames, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapInto, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + + # + # Controversial and experimental checks (opt-in, just replace `false` with `[]`) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, + {Credo.Check.Consistency.UnusedVariableNames, false}, + {Credo.Check.Design.DuplicatedCode, false}, + {Credo.Check.Readability.AliasAs, false}, + {Credo.Check.Readability.MultiAlias, false}, + {Credo.Check.Readability.Specs, false}, + {Credo.Check.Readability.SinglePipe, false}, + {Credo.Check.Refactor.ABCSize, false}, + {Credo.Check.Refactor.AppendSingleItem, false}, + {Credo.Check.Refactor.DoubleBooleanNegation, false}, + {Credo.Check.Refactor.ModuleDependencies, false}, + {Credo.Check.Refactor.PipeChainStart, false}, + {Credo.Check.Refactor.VariableRebinding, false}, + {Credo.Check.Warning.MapGetUnsafePass, false}, + {Credo.Check.Warning.UnsafeToAtom, false} + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + ] +} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..a580fed --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,40 @@ +image: elixir:1.9 + +variables: + MIX_ENV: test + +cache: + key: ${CI_COMMIT_REF_SLUG} + paths: + - deps + - _build +stages: + - build + - test + +before_script: + - mix local.hex --force + - mix local.rebar --force + +build: + stage: build + script: + - mix deps.get + - mix compile --force + +unit-testing: + stage: test + coverage: '/(\d+\.\d+\%) \| Total/' + script: + - mix test --trace --cover + +lint: + stage: test + script: + - mix format --check-formatted + +analysis: + stage: test + script: + - mix deps.get + - mix credo --strict diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2543ae7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## Unreleased + +Initial release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md index 5886716..7cf6b50 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Concurrent Limiter -See the docs in `lib/concurrent_limiter.ex`. +See the docs at [hexdocs.pm/concurrent_limiter](https://hexdocs.pm/concurrent_limiter/) or in `lib/concurrent_limiter.ex`. diff --git a/lib/concurrent_limiter.ex b/lib/concurrent_limiter.ex index b4572c6..9555f00 100644 --- a/lib/concurrent_limiter.ex +++ b/lib/concurrent_limiter.ex @@ -1,54 +1,71 @@ +# ConcurrentLimiter: A concurrency limiter. +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: LGPL-3.0-only + defmodule ConcurrentLimiter do require Logger @moduledoc """ - # Concurrent Limiter - A concurrency limiter. Limits the number of concurrent invocations possible, without using a worker pool or different processes. It can be useful in cases where you don't need a worker pool but still being able to limit concurrent calls without much overhead. - As it internally uses `persistent_term` to store metadata, and can fallback to ETS tables, it is however not made for a large number - of limiters and cannot be used for things like a per-user rate limiter. - """ + As it internally uses `persistent_term` to store metadata, it is not made for a large number of different or dynamic limiters and + cannot be used for things like a per-user rate limiter. - @doc """ - Initializes a `ConcurrentLimiter`. + ```elixir + :ok = ConcurrentLimiter.new(RequestLimiter, 10, 10) + ConcurrentLimiter.limit(RequestLimiter, fn() -> something_that_can_only_run_ten_times_concurrently() end) + ``` """ @default_wait 150 @default_max_retries 5 - @spec new(name, max_running, max_waiting, options) :: :ok | {:error, :existing} when name: atom(), - max_running: non_neg_integer(), - max_waiting: non_neg_integer() | :infinity, - options: [option], - option: {:wait, non_neg_integer()} | {:max_retries, non_neg_integer()} + @doc "Initializes a `ConcurrentLimiter`." + @spec new(name, max_running, max_waiting, options) :: :ok | {:error, :existing} + when name: atom(), + max_running: non_neg_integer(), + max_waiting: non_neg_integer() | :infinity, + options: [option], + option: {:wait, non_neg_integer()} | {:max_retries, non_neg_integer()} def new(name, max_running, max_waiting, options \\ []) do name = prefix_name(name) + if defined?(name) do {:error, :existing} else wait = Keyword.get(options, :wait, @default_wait) max_retries = Keyword.get(options, :max_retries, @default_max_retries) - atomics = :atomics.new(1, [signed: true]) - :persistent_term.put(name, {__MODULE__, max_running, max_waiting, atomics, wait, max_retries}) + atomics = :atomics.new(1, signed: true) + + :persistent_term.put( + name, + {__MODULE__, max_running, max_waiting, atomics, wait, max_retries} + ) + :ok end end - @spec set(name, new_max_running, new_max_waiting, options) :: :ok | :error when name: atom(), - new_max_running: non_neg_integer(), - new_max_waiting: non_neg_integer() | :infinity, - options: [option], - option: {:wait, non_neg_integer()} - @doc "Adjust the limiter limits at runtime" + @doc "Adjust the limits at runtime." + @spec set(name, new_max_running, new_max_waiting, options) :: :ok | :error + when name: atom(), + new_max_running: non_neg_integer(), + new_max_waiting: non_neg_integer() | :infinity, + options: [option], + option: {:wait, non_neg_integer()} def set(name, new_max_running, new_max_waiting, options \\ []) do name = prefix_name(name) + if defined?(name) do new_wait = Keyword.get(options, :wait) new_max_retries = Keyword.get(options, :max_retries) {__MODULE__, max_running, max_waiting, ref, wait, max_retries} = :persistent_term.get(name) - new = {__MODULE__, new_max_running || max_running, new_max_waiting || max_waiting, ref, new_wait || wait, new_max_retries || max_retries} + + new = + {__MODULE__, new_max_running || max_running, new_max_waiting || max_waiting, ref, + new_wait || wait, new_max_retries || max_retries} + :persistent_term.put(name, new) :ok else @@ -56,9 +73,10 @@ defmodule ConcurrentLimiter do end end - @spec limit(atom(), function(), opts) :: {:error, :overload} | any() when opts: [option], - option: {:wait, non_neg_integer()} | {:max_retries, non_neg_integer()} @doc "Limits invocation of `fun`." + @spec limit(atom(), function(), opts) :: {:error, :overload} | any() + when opts: [option], + option: {:wait, non_neg_integer()} | {:max_retries, non_neg_integer()} def limit(name, fun, opts \\ []) do do_limit(prefix_name(name), fun, opts, 0) end @@ -85,11 +103,11 @@ defmodule ConcurrentLimiter do {:error, :overload} counter > max_running -> - wait(ref, name, wait, fun, opts, max_retries, retries + 1) + wait(ref, name, fun, wait, opts, retries + 1) end end - defp wait(ref, name, wait, fun, opts, max_retries, retries) do + defp wait(ref, name, fun, wait, opts, retries) do wait = Keyword.get(opts, :timeout) || wait Process.sleep(wait) dec(ref, name) @@ -112,5 +130,4 @@ defmodule ConcurrentLimiter do rescue _ -> false end - end diff --git a/mix.exs b/mix.exs index 49378b6..93dc17e 100644 --- a/mix.exs +++ b/mix.exs @@ -1,13 +1,24 @@ defmodule ConcurrentLimiter.MixProject do use Mix.Project + @repo "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter" def project do [ app: :concurrent_limiter, version: "0.1.0", - elixir: "~> 1.10", + elixir: "~> 1.9", start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + package: package(), + # Docs + name: "Concurrent Limiter", + source_url: @repo, + homepage_url: @repo, + docs: [ + main: "ConcurrentLimiter", + extras: [], + source_url_pattern: @repo <> "/blob/master/%{path}#L%{line}" + ] ] end @@ -19,8 +30,16 @@ defmodule ConcurrentLimiter.MixProject do defp deps do [ + {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false}, {:ex_doc, "~> 0.21", only: :dev, runtime: false}, {:benchee, "~> 1.0", only: [:dev, :test]} ] end + + defp package do + [ + licenses: ["LGPLv3"], + links: %{"GitLab" => @repo} + ] + end end diff --git a/mix.lock b/mix.lock index fa32375..98a2640 100644 --- a/mix.lock +++ b/mix.lock @@ -1,8 +1,11 @@ %{ "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "credo": {:hex, :credo, "1.1.5", "caec7a3cadd2e58609d7ee25b3931b129e739e070539ad1a0cd7efeeb47014f4", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "d0bbd3222607ccaaac5c0340f7f525c627ae4d7aee6c8c8c108922620c5b6446"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, + "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, "makeup": {:hex, :makeup, "1.0.1", "82f332e461dc6c79dbd82fbe2a9c10d48ed07146f0a478286e590c83c52010b5", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "49736fe5b66a08d8575bf5321d716bac5da20c8e6b97714fec2bcd6febcfa1f8"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, diff --git a/test/concurrent_limiter_test.exs b/test/concurrent_limiter_test.exs index e1d281e..090417e 100644 --- a/test/concurrent_limiter_test.exs +++ b/test/concurrent_limiter_test.exs @@ -1,42 +1,39 @@ +# ConcurrentLimiter: A concurrency limiter. +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: LGPL-3.0-only + defmodule ConcurrentLimiterTest do use ExUnit.Case doctest ConcurrentLimiter - test "limiter ets is atomic" do - name = "test1" + test "limiter is atomic" do + name = "test" ConcurrentLimiter.new(name, 2, 2) - atomic_test(name) - end - - test "limiter atomics is atomic" do - name = "test2" - ConcurrentLimiter.new(name, 2, 2, backend: :atomics) - atomic_test(name) - end - - defp atomic_test(name) do self = self() - sleepy = fn sleep -> - case ConcurrentLimiter.limit(name, fn -> - send(self, :ok) - Process.sleep(sleep) - :ok - end) do - :ok -> :ok - other -> send(self, other) - end - end - - spawn_link(fn -> sleepy.(500) end) - spawn_link(fn -> sleepy.(500) end) - spawn_link(fn -> sleepy.(500) end) - spawn_link(fn -> sleepy.(500) end) - spawn_link(fn -> sleepy.(500) end) + spawn_link(fn -> sleepy(name, 500) end) + spawn_link(fn -> sleepy(name, 500) end) + spawn_link(fn -> sleepy(name, 500) end) + spawn_link(fn -> sleepy(name, 500) end) + spawn_link(fn -> sleepy(name, 500) end) assert_receive :ok, 2000 assert_receive :ok, 2000 assert_receive {:error, :overload}, 2000 assert_receive :ok, 2000 assert_receive :ok, 2000 end + + defp sleepy(duration) do + result = + ConcurrentLimiter.limit(name, fn -> + send(self, :ok) + Process.sleep(sleep) + :ok + end) + + case result do + :ok -> :ok + other -> send(self, other) + end + end end diff --git a/test/samples/multi_limiter.exs b/test/samples/multi_limiter.exs index f56cb6c..0936773 100644 --- a/test/samples/multi_limiter.exs +++ b/test/samples/multi_limiter.exs @@ -1,49 +1,54 @@ infinite = 1_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000 -parallel = case Integer.parse(System.get_env("PARALLEL", "")) do - {int, _} -> int - _ -> System.schedulers_online()/2 -end -multi_count = case Integer.parse(System.get_env("MULTI", "")) do - {int, _} -> int - _ -> parallel -end +parallel = + case Integer.parse(System.get_env("PARALLEL", "")) do + {int, _} -> int + _ -> System.schedulers_online() / 2 + end + +multi_count = + case Integer.parse(System.get_env("MULTI", "")) do + {int, _} -> int + _ -> parallel + end -names = fn(prefix) -> +names = fn prefix -> for i <- 1..multi_count do Module.concat(MultiConcurrentLimiterBenchmark, "#{prefix}#{i}") end end +bench_unique = + for name <- names.("u") do + ConcurrentLimiter.new(name, infinite, 0, backend: {:ets, name, []}) + name + end -bench_unique = for name <- names.("u") do - ConcurrentLimiter.new(name, infinite, 0, backend: {:ets, name, []}) - name -end - -IO.inspect(bench_unique) - -bench_atomics = for name <- names.("a") do - ConcurrentLimiter.new(name, infinite, 0, backend: :atomics) - name -end +bench_atomics = + for name <- names.("a") do + ConcurrentLimiter.new(name, infinite, 0, backend: :atomics) + name + end -bench_shared = for name <- names.("s") do - ConcurrentLimiter.new(name, infinite, 0, backend: {:ets, ConcurrentLimiterTest, []}) - name -end +bench_shared = + for name <- names.("s") do + ConcurrentLimiter.new(name, infinite, 0, backend: {:ets, ConcurrentLimiterTest, []}) + name + end rw = [{:read_concurrency, true}, {:write_concurrency, true}] -bench_unique_rw = for name <- names.("u_rw") do - ConcurrentLimiter.new(name, infinite, 0, backend: {:ets, name, rw}) - name -end +bench_unique_rw = + for name <- names.("u_rw") do + ConcurrentLimiter.new(name, infinite, 0, backend: {:ets, name, rw}) + name + end -bench_shared_rw = for name <- names.("s_rw") do - ConcurrentLimiter.new(name, infinite, 0, backend: {:ets, ConcurrentLimiterTestRW, rw}) - name -end +bench_shared_rw = + for name <- names.("s_rw") do + ConcurrentLimiter.new(name, infinite, 0, backend: {:ets, ConcurrentLimiterTestRW, rw}) + name + end multiple = %{ "ConcurrentLimiter.limit/2 unique ets" => fn -> diff --git a/test/samples/update_counter.exs b/test/samples/update_counter.exs index 6310c57..1d8e837 100644 --- a/test/samples/update_counter.exs +++ b/test/samples/update_counter.exs @@ -1,20 +1,25 @@ :ets.new(:limiter_bench, [:public, :named_table]) -:ets.new(:limiter_bench_concurrent, [:public, :named_table, {:read_concurrency, false}, {:write_concurrency, true}]) + +:ets.new(:limiter_bench_concurrent, [ + :public, + :named_table, + {:read_concurrency, false}, + {:write_concurrency, true} +]) + atomics = :atomics.new(1, []) -update_counter = - %{ - "ets:update_counter" => fn -> - :ets.update_counter(:limiter_bench, "bench", {2, 1}, {"bench", 0}) - end, - "ets:update_counter concurrent" => fn -> - :ets.update_counter(:limiter_bench, "bench", {2, 1}, {"bench", 0}) - end, - "atomics:add_get" => fn -> - :atomics.add_get(atomics, 1, 1) - end, - } +update_counter = %{ + "ets:update_counter" => fn -> + :ets.update_counter(:limiter_bench, "bench", {2, 1}, {"bench", 0}) + end, + "ets:update_counter concurrent" => fn -> + :ets.update_counter(:limiter_bench, "bench", {2, 1}, {"bench", 0}) + end, + "atomics:add_get" => fn -> + :atomics.add_get(atomics, 1, 1) + end +} Benchee.run(update_counter, parallel: 1) Benchee.run(update_counter, parallel: System.schedulers_online()) - -- cgit v1.2.3