summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgregor herrmann <gregoa@debian.org>2021-12-06 20:17:17 +0100
committergregor herrmann <gregoa@debian.org>2021-12-06 20:17:17 +0100
commitc372bb5ac247c9e8e3ce001ad8fcacddf47e4578 (patch)
tree04d87366b4c58deeb6e695d9620ec6b850f7808c
Import libalgorithm-backoff-perl_0.009.orig.tar.gz
[dgit import orig libalgorithm-backoff-perl_0.009.orig.tar.gz]
-rw-r--r--Changes78
-rw-r--r--LICENSE379
-rw-r--r--MANIFEST31
-rw-r--r--META.json590
-rw-r--r--META.yml439
-rw-r--r--Makefile.PL63
-rw-r--r--README120
-rw-r--r--dist.ini23
-rw-r--r--lib/Algorithm/Backoff.pm410
-rw-r--r--lib/Algorithm/Backoff/Constant.pm221
-rw-r--r--lib/Algorithm/Backoff/Exponential.pm249
-rw-r--r--lib/Algorithm/Backoff/Fibonacci.pm267
-rw-r--r--lib/Algorithm/Backoff/LILD.pm271
-rw-r--r--lib/Algorithm/Backoff/LIMD.pm269
-rw-r--r--lib/Algorithm/Backoff/MILD.pm272
-rw-r--r--lib/Algorithm/Backoff/MIMD.pm274
-rw-r--r--t/00-compile.t67
-rw-r--r--t/01-base.t141
-rw-r--r--t/author-critic.t15
-rw-r--r--t/author-pod-coverage.t15
-rw-r--r--t/author-pod-syntax.t15
-rw-r--r--t/constant.t38
-rw-r--r--t/exponential.t43
-rw-r--r--t/fibonacci.t34
-rw-r--r--t/lild.t36
-rw-r--r--t/limd.t35
-rw-r--r--t/mild.t40
-rw-r--r--t/mimd.t37
-rw-r--r--t/release-rinci.t19
-rw-r--r--weaver.ini1
30 files changed, 4492 insertions, 0 deletions
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..5974d90
--- /dev/null
+++ b/Changes
@@ -0,0 +1,78 @@
+0.009 2019-06-20 (PERLANCAR)
+
+ - No functional changes.
+
+ - Fix typos in Synopsis.
+
+
+0.008 2019-06-20 (PERLANCAR)
+
+ - No functional changes.
+
+ - [doc] Mention Retry::Backoff and App::AlgorithmBackoffUtils.
+
+
+0.007 2019-06-18 (PERLANCAR)
+
+ - No functional changes.
+
+ - [doc] Mention Action::Retry.
+
+
+0.006 2019-06-08 (PERLANCAR)
+
+ - Add strategies: LILD, LIMD, MILD, MIMD.
+
+ - Make max_delay and min_delay a common attribute for all strategies.
+
+ - [bugfix] jittered delay shouldn't have violated min_delay and
+ max_delay.
+
+ - [refactor] Refactor to reduce code duplication.
+
+ - [doc] Show illustrations using show-backoff-delays CLI in
+ Synopsis.
+
+ - [meta] No longer mark delay_on_success as common attribute, because we
+ now have algorithms that have varying delays on success.
+
+
+0.005 2019-06-07 (PERLANCAR)
+
+ - No functional changes.
+
+ - [doc] Minor tweaks.
+
+ - [meta] Add tag to common attributes.
+
+
+0.004 2019-06-05 (PERLANCAR)
+
+ - Add common attribute: max_actual_duration.
+
+ - [doc] Update variable name in Synopsis.
+
+
+0.003 2019-04-10 (PERLANCAR)
+
+ - Rename module/distro from Algorithm-Retry -> Algorithm-Backoff.
+
+ - Rename strategy ExponentialBackoff -> Exponential.
+
+
+0.002 2019-04-10 (PERLANCAR)
+
+ - [Incompatible change][ux] Constant: Rename attribute delay_on_failure
+ to delay.
+
+ - Add strategy: Fibonacci.
+
+ - Make behavior of consider_actual_delay configurable as an
+ attribute.
+
+ - [bugfix] Fix setting default for timestamp.
+
+
+0.001 2019-04-08 (PERLANCAR)
+
+ - First release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cb00a54
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,379 @@
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+Terms of the Perl programming language system itself
+
+a) the GNU General Public License as published by the Free
+ Software Foundation; either version 1, or (at your option) any
+ later version, or
+b) the "Artistic License"
+
+--- The GNU General Public License, Version 1, February 1989 ---
+
+This software is Copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software, licensed under:
+
+ The GNU General Public License, Version 1, February 1989
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 1, February 1989
+
+ Copyright (C) 1989 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The license agreements of most software companies try to keep users
+at the mercy of those companies. By contrast, our General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. The
+General Public License applies to the Free Software Foundation's
+software and to any other program whose authors commit to using it.
+You can use it for your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Specifically, the General Public License is designed to make
+sure that you have the freedom to give away or sell copies of free
+software, that you receive source code or can get it if you want it,
+that you can change the software or use pieces of it in new free
+programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of a such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must tell them their rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any program or other work which
+contains a notice placed by the copyright holder saying it may be
+distributed under the terms of this General Public License. The
+"Program", below, refers to any such program or work, and a "work based
+on the Program" means either the Program or any work containing the
+Program or a portion of it, either verbatim or with modifications. Each
+licensee is addressed as "you".
+
+ 1. You may copy and distribute verbatim copies of the Program's source
+code as you receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice and
+disclaimer of warranty; keep intact all the notices that refer to this
+General Public License and to the absence of any warranty; and give any
+other recipients of the Program a copy of this General Public License
+along with the Program. You may charge a fee for the physical act of
+transferring a copy.
+
+ 2. You may modify your copy or copies of the Program or any portion of
+it, and copy and distribute such modifications under the terms of Paragraph
+1 above, provided that you also do the following:
+
+ a) cause the modified files to carry prominent notices stating that
+ you changed the files and the date of any change; and
+
+ b) cause the whole of any work that you distribute or publish, that
+ in whole or in part contains the Program or any part thereof, either
+ with or without modifications, to be licensed at no charge to all
+ third parties under the terms of this General Public License (except
+ that you may choose to grant warranty protection to some or all
+ third parties, at your option).
+
+ c) If the modified program normally reads commands interactively when
+ run, you must cause it, when started running for such interactive use
+ in the simplest and most usual way, to print or display an
+ announcement including an appropriate copyright notice and a notice
+ that there is no warranty (or else, saying that you provide a
+ warranty) and that users may redistribute the program under these
+ conditions, and telling the user how to view a copy of this General
+ Public License.
+
+ d) You may charge a fee for the physical act of transferring a
+ copy, and you may at your option offer warranty protection in
+ exchange for a fee.
+
+Mere aggregation of another independent work with the Program (or its
+derivative) on a volume of a storage or distribution medium does not bring
+the other work under the scope of these terms.
+
+ 3. You may copy and distribute the Program (or a portion or derivative of
+it, under Paragraph 2) in object code or executable form under the terms of
+Paragraphs 1 and 2 above provided that you also do one of the following:
+
+ a) accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of
+ Paragraphs 1 and 2 above; or,
+
+ b) accompany it with a written offer, valid for at least three
+ years, to give any third party free (except for a nominal charge
+ for the cost of distribution) a complete machine-readable copy of the
+ corresponding source code, to be distributed under the terms of
+ Paragraphs 1 and 2 above; or,
+
+ c) accompany it with the information you received as to where the
+ corresponding source code may be obtained. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form alone.)
+
+Source code for a work means the preferred form of the work for making
+modifications to it. For an executable file, complete source code means
+all the source code for all modules it contains; but, as a special
+exception, it need not include source code for modules which are standard
+libraries that accompany the operating system on which the executable
+file runs, or for standard header files or definitions files that
+accompany that operating system.
+
+ 4. You may not copy, modify, sublicense, distribute or transfer the
+Program except as expressly provided under this General Public License.
+Any attempt otherwise to copy, modify, sublicense, distribute or transfer
+the Program is void, and will automatically terminate your rights to use
+the Program under this License. However, parties who have received
+copies, or rights to use copies, from you under this General Public
+License will not have their licenses terminated so long as such parties
+remain in full compliance.
+
+ 5. By copying, distributing or modifying the Program (or any work based
+on the Program) you indicate your acceptance of this license to do so,
+and all its terms and conditions.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the original
+licensor to copy, distribute or modify the Program subject to these
+terms and conditions. You may not impose any further restrictions on the
+recipients' exercise of the rights granted herein.
+
+ 7. The Free Software Foundation may publish revised and/or new versions
+of the 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 Program
+specifies a version number of the license which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+the license, you may choose any version ever published by the Free Software
+Foundation.
+
+ 8. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to humanity, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these
+terms.
+
+ To do so, attach the following notices to the program. It is safest to
+attach them to the start of each source file to most effectively convey
+the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 1, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19xx name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the
+appropriate parts of the General Public License. Of course, the
+commands you use may be called something other than `show w' and `show
+c'; they could even be mouse-clicks or menu items--whatever suits your
+program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ program `Gnomovision' (a program to direct compilers to make passes
+ at assemblers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
+--- The Artistic License 1.0 ---
+
+This software is Copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software, licensed under:
+
+ The Artistic License 1.0
+
+The Artistic License
+
+Preamble
+
+The intent of this document is to state the conditions under which a Package
+may be copied, such that the Copyright Holder maintains some semblance of
+artistic control over the development of the package, while giving the users of
+the package the right to use and distribute the Package in a more-or-less
+customary fashion, plus the right to make reasonable modifications.
+
+Definitions:
+
+ - "Package" refers to the collection of files distributed by the Copyright
+ Holder, and derivatives of that collection of files created through
+ textual modification.
+ - "Standard Version" refers to such a Package if it has not been modified,
+ or has been modified in accordance with the wishes of the Copyright
+ Holder.
+ - "Copyright Holder" is whoever is named in the copyright or copyrights for
+ the package.
+ - "You" is you, if you're thinking about copying or distributing this Package.
+ - "Reasonable copying fee" is whatever you can justify on the basis of media
+ cost, duplication charges, time of people involved, and so on. (You will
+ not be required to justify it to the Copyright Holder, but only to the
+ computing community at large as a market that must bear the fee.)
+ - "Freely Available" means that no fee is charged for the item itself, though
+ there may be fees involved in handling the item. It also means that
+ recipients of the item may redistribute it under the same conditions they
+ received it.
+
+1. You may make and give away verbatim copies of the source form of the
+Standard Version of this Package without restriction, provided that you
+duplicate all of the original copyright notices and associated disclaimers.
+
+2. You may apply bug fixes, portability fixes and other modifications derived
+from the Public Domain or from the Copyright Holder. A Package modified in such
+a way shall still be considered the Standard Version.
+
+3. You may otherwise modify your copy of this Package in any way, provided that
+you insert a prominent notice in each changed file stating how and when you
+changed that file, and provided that you do at least ONE of the following:
+
+ a) place your modifications in the Public Domain or otherwise make them
+ Freely Available, such as by posting said modifications to Usenet or an
+ equivalent medium, or placing the modifications on a major archive site
+ such as ftp.uu.net, or by allowing the Copyright Holder to include your
+ modifications in the Standard Version of the Package.
+
+ b) use the modified Package only within your corporation or organization.
+
+ c) rename any non-standard executables so the names do not conflict with
+ standard executables, which must also be provided, and provide a separate
+ manual page for each non-standard executable that clearly documents how it
+ differs from the Standard Version.
+
+ d) make other distribution arrangements with the Copyright Holder.
+
+4. You may distribute the programs of this Package in object code or executable
+form, provided that you do at least ONE of the following:
+
+ a) distribute a Standard Version of the executables and library files,
+ together with instructions (in the manual page or equivalent) on where to
+ get the Standard Version.
+
+ b) accompany the distribution with the machine-readable source of the Package
+ with your modifications.
+
+ c) accompany any non-standard executables with their corresponding Standard
+ Version executables, giving the non-standard executables non-standard
+ names, and clearly documenting the differences in manual pages (or
+ equivalent), together with instructions on where to get the Standard
+ Version.
+
+ d) make other distribution arrangements with the Copyright Holder.
+
+5. You may charge a reasonable copying fee for any distribution of this
+Package. You may charge any fee you choose for support of this Package. You
+may not charge a fee for this Package itself. However, you may distribute this
+Package in aggregate with other (possibly commercial) programs as part of a
+larger (possibly commercial) software distribution provided that you do not
+advertise this Package as a product of your own.
+
+6. The scripts and library files supplied as input to or produced as output
+from the programs of this Package do not automatically fall under the copyright
+of this Package, but belong to whomever generated them, and may be sold
+commercially, and may be aggregated with this Package.
+
+7. C or perl subroutines supplied by you and linked into this Package shall not
+be considered part of this Package.
+
+8. The name of the Copyright Holder may not be used to endorse or promote
+products derived from this software without specific prior written permission.
+
+9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+The End
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..4cb30ac
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,31 @@
+# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.012.
+Changes
+LICENSE
+MANIFEST
+META.json
+META.yml
+Makefile.PL
+README
+dist.ini
+lib/Algorithm/Backoff.pm
+lib/Algorithm/Backoff/Constant.pm
+lib/Algorithm/Backoff/Exponential.pm
+lib/Algorithm/Backoff/Fibonacci.pm
+lib/Algorithm/Backoff/LILD.pm
+lib/Algorithm/Backoff/LIMD.pm
+lib/Algorithm/Backoff/MILD.pm
+lib/Algorithm/Backoff/MIMD.pm
+t/00-compile.t
+t/01-base.t
+t/author-critic.t
+t/author-pod-coverage.t
+t/author-pod-syntax.t
+t/constant.t
+t/exponential.t
+t/fibonacci.t
+t/lild.t
+t/limd.t
+t/mild.t
+t/mimd.t
+t/release-rinci.t
+weaver.ini
diff --git a/META.json b/META.json
new file mode 100644
index 0000000..f5c2c8d
--- /dev/null
+++ b/META.json
@@ -0,0 +1,590 @@
+{
+ "abstract" : "Various backoff strategies for retry",
+ "author" : [
+ "perlancar <perlancar@cpan.org>"
+ ],
+ "dynamic_config" : 0,
+ "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010",
+ "license" : [
+ "perl_5"
+ ],
+ "meta-spec" : {
+ "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
+ "version" : 2
+ },
+ "name" : "Algorithm-Backoff",
+ "prereqs" : {
+ "configure" : {
+ "requires" : {
+ "ExtUtils::MakeMaker" : "0"
+ }
+ },
+ "develop" : {
+ "requires" : {
+ "Pod::Coverage::TrustPod" : "0",
+ "Test::Perl::Critic" : "0",
+ "Test::Pod" : "1.41",
+ "Test::Pod::Coverage" : "1.08",
+ "Test::Rinci" : "0.151"
+ },
+ "x_spec" : {
+ "Rinci" : "v1.1.90"
+ }
+ },
+ "runtime" : {
+ "requires" : {
+ "Time::HiRes" : "0",
+ "parent" : "0",
+ "perl" : "5.010001",
+ "strict" : "0",
+ "warnings" : "0"
+ }
+ },
+ "test" : {
+ "requires" : {
+ "File::Spec" : "0",
+ "IO::Handle" : "0",
+ "IPC::Open3" : "0",
+ "Test::Exception" : "0",
+ "Test::More" : "0.98",
+ "Test::Number::Delta" : "0"
+ }
+ }
+ },
+ "release_status" : "stable",
+ "resources" : {
+ "bugtracker" : {
+ "web" : "https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff"
+ },
+ "homepage" : "https://metacpan.org/release/Algorithm-Backoff",
+ "repository" : {
+ "type" : "git",
+ "url" : "git://github.com/perlancar/perl-Algorithm-Backoff.git",
+ "web" : "https://github.com/perlancar/perl-Algorithm-Backoff"
+ }
+ },
+ "version" : "0.009",
+ "x_Dist_Zilla" : {
+ "perl" : {
+ "version" : "5.028002"
+ },
+ "plugins" : [
+ {
+ "class" : "Dist::Zilla::Plugin::GatherDir",
+ "config" : {
+ "Dist::Zilla::Plugin::GatherDir" : {
+ "exclude_filename" : [],
+ "exclude_match" : [],
+ "follow_symlinks" : 0,
+ "include_dotfiles" : 0,
+ "prefix" : "",
+ "prune_directory" : [],
+ "root" : "."
+ }
+ },
+ "name" : "@Author::PERLANCAR/@Filter/GatherDir",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PruneCruft",
+ "name" : "@Author::PERLANCAR/@Filter/PruneCruft",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::ManifestSkip",
+ "name" : "@Author::PERLANCAR/@Filter/ManifestSkip",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::MetaYAML",
+ "name" : "@Author::PERLANCAR/@Filter/MetaYAML",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::License",
+ "name" : "@Author::PERLANCAR/@Filter/License",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PodCoverageTests",
+ "name" : "@Author::PERLANCAR/@Filter/PodCoverageTests",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PodSyntaxTests",
+ "name" : "@Author::PERLANCAR/@Filter/PodSyntaxTests",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::ExtraTests",
+ "name" : "@Author::PERLANCAR/@Filter/ExtraTests",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::ExecDir",
+ "name" : "@Author::PERLANCAR/@Filter/ExecDir",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::ShareDir",
+ "name" : "@Author::PERLANCAR/@Filter/ShareDir",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::MakeMaker",
+ "config" : {
+ "Dist::Zilla::Role::TestRunner" : {
+ "default_jobs" : 1
+ }
+ },
+ "name" : "@Author::PERLANCAR/@Filter/MakeMaker",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Manifest",
+ "name" : "@Author::PERLANCAR/@Filter/Manifest",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::ConfirmRelease",
+ "name" : "@Author::PERLANCAR/@Filter/ConfirmRelease",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::ExecDir",
+ "name" : "@Author::PERLANCAR/ExecDir script",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PERLANCAR::BeforeBuild",
+ "name" : "@Author::PERLANCAR/PERLANCAR::BeforeBuild",
+ "version" : "0.596"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Rinci::AbstractFromMeta",
+ "name" : "@Author::PERLANCAR/Rinci::AbstractFromMeta",
+ "version" : "0.10"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PodnameFromFilename",
+ "name" : "@Author::PERLANCAR/PodnameFromFilename",
+ "version" : "0.02"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PERLANCAR::EnsurePrereqToSpec",
+ "name" : "@Author::PERLANCAR/PERLANCAR::EnsurePrereqToSpec",
+ "version" : "0.060"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PERLANCAR::MetaResources",
+ "name" : "@Author::PERLANCAR/PERLANCAR::MetaResources",
+ "version" : "0.040"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::CheckChangeLog",
+ "name" : "@Author::PERLANCAR/CheckChangeLog",
+ "version" : "0.05"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::CheckMetaResources",
+ "name" : "@Author::PERLANCAR/CheckMetaResources",
+ "version" : "0.001"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::CheckSelfDependency",
+ "config" : {
+ "Dist::Zilla::Plugin::CheckSelfDependency" : {
+ "finder" : [
+ ":InstallModules"
+ ]
+ },
+ "Dist::Zilla::Role::ModuleMetadata" : {
+ "Module::Metadata" : "1.000033",
+ "version" : "0.006"
+ }
+ },
+ "name" : "@Author::PERLANCAR/CheckSelfDependency",
+ "version" : "0.011"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::CopyrightYearFromGit",
+ "name" : "@Author::PERLANCAR/CopyrightYearFromGit",
+ "version" : "0.003"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::IfBuilt",
+ "name" : "@Author::PERLANCAR/IfBuilt",
+ "version" : "0.02"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::MetaJSON",
+ "name" : "@Author::PERLANCAR/MetaJSON",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::MetaConfig",
+ "name" : "@Author::PERLANCAR/MetaConfig",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Authority",
+ "name" : "@Author::PERLANCAR/Authority",
+ "version" : "1.009"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::OurDate",
+ "name" : "@Author::PERLANCAR/OurDate",
+ "version" : "0.03"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::OurDist",
+ "name" : "@Author::PERLANCAR/OurDist",
+ "version" : "0.02"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::OurPkgVersion",
+ "name" : "@Author::PERLANCAR/OurPkgVersion",
+ "version" : "0.15"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PodWeaver",
+ "config" : {
+ "Dist::Zilla::Plugin::PodWeaver" : {
+ "finder" : [
+ ":InstallModules",
+ ":ExecFiles"
+ ],
+ "plugins" : [
+ {
+ "class" : "Pod::Weaver::Plugin::EnsurePod5",
+ "name" : "@CorePrep/EnsurePod5",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Plugin::H1Nester",
+ "name" : "@CorePrep/H1Nester",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Name",
+ "name" : "@Author::PERLANCAR/Name",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Version",
+ "name" : "@Author::PERLANCAR/Version",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Region",
+ "name" : "@Author::PERLANCAR/prelude",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Generic",
+ "name" : "SYNOPSIS",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Generic",
+ "name" : "DESCRIPTION",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Generic",
+ "name" : "OVERVIEW",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Collect",
+ "name" : "ATTRIBUTES",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Collect",
+ "name" : "METHODS",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Collect",
+ "name" : "FUNCTIONS",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Leftovers",
+ "name" : "@Author::PERLANCAR/Leftovers",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Region",
+ "name" : "@Author::PERLANCAR/postlude",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Completion::GetoptLongComplete",
+ "name" : "@Author::PERLANCAR/Completion::GetoptLongComplete",
+ "version" : "0.08"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Completion::GetoptLongSubcommand",
+ "name" : "@Author::PERLANCAR/Completion::GetoptLongSubcommand",
+ "version" : "0.04"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Completion::GetoptLongMore",
+ "name" : "@Author::PERLANCAR/Completion::GetoptLongMore",
+ "version" : "0.001"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Homepage::DefaultCPAN",
+ "name" : "@Author::PERLANCAR/Homepage::DefaultCPAN",
+ "version" : "0.05"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Source::DefaultGitHub",
+ "name" : "@Author::PERLANCAR/Source::DefaultGitHub",
+ "version" : "0.07"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Bugs::DefaultRT",
+ "name" : "@Author::PERLANCAR/Bugs::DefaultRT",
+ "version" : "0.06"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Authors",
+ "name" : "@Author::PERLANCAR/Authors",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Section::Legal",
+ "name" : "@Author::PERLANCAR/Legal",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Plugin::Rinci",
+ "name" : "@Author::PERLANCAR/Rinci",
+ "version" : "0.780"
+ },
+ {
+ "class" : "Pod::Weaver::Plugin::AppendPrepend",
+ "name" : "@Author::PERLANCAR/AppendPrepend",
+ "version" : "0.01"
+ },
+ {
+ "class" : "Pod::Weaver::Plugin::EnsureUniqueSections",
+ "name" : "@Author::PERLANCAR/EnsureUniqueSections",
+ "version" : "0.163250"
+ },
+ {
+ "class" : "Pod::Weaver::Plugin::SingleEncoding",
+ "name" : "@Author::PERLANCAR/SingleEncoding",
+ "version" : "4.015"
+ },
+ {
+ "class" : "Pod::Weaver::Plugin::PERLANCAR::SortSections",
+ "name" : "@Author::PERLANCAR/PERLANCAR::SortSections",
+ "version" : "0.06"
+ }
+ ]
+ }
+ },
+ "name" : "@Author::PERLANCAR/PodWeaver",
+ "version" : "4.008"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::PruneFiles",
+ "name" : "@Author::PERLANCAR/PruneFiles",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Pod2Readme",
+ "name" : "@Author::PERLANCAR/Pod2Readme",
+ "version" : "0.004"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Rinci::AddPrereqs",
+ "name" : "@Author::PERLANCAR/Rinci::AddPrereqs",
+ "version" : "0.142"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Rinci::AddToDb",
+ "name" : "@Author::PERLANCAR/Rinci::AddToDb",
+ "version" : "0.01"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Rinci::Validate",
+ "name" : "@Author::PERLANCAR/Rinci::Validate",
+ "version" : "0.24"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::SetScriptShebang",
+ "name" : "@Author::PERLANCAR/SetScriptShebang",
+ "version" : "0.01"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Test::Compile",
+ "config" : {
+ "Dist::Zilla::Plugin::Test::Compile" : {
+ "bail_out_on_fail" : 0,
+ "fail_on_warning" : "author",
+ "fake_home" : 0,
+ "filename" : "t/00-compile.t",
+ "module_finder" : [
+ ":InstallModules"
+ ],
+ "needs_display" : 0,
+ "phase" : "test",
+ "script_finder" : [
+ ":PerlExecFiles"
+ ],
+ "skips" : [],
+ "switch" : []
+ }
+ },
+ "name" : "@Author::PERLANCAR/Test::Compile",
+ "version" : "2.058"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Test::Perl::Critic",
+ "name" : "@Author::PERLANCAR/Test::Perl::Critic",
+ "version" : "3.001"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Test::Rinci",
+ "name" : "@Author::PERLANCAR/Test::Rinci",
+ "version" : "0.040"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::StaticInstall",
+ "config" : {
+ "Dist::Zilla::Plugin::StaticInstall" : {
+ "dry_run" : 0,
+ "mode" : "on"
+ }
+ },
+ "name" : "@Author::PERLANCAR/StaticInstall",
+ "version" : "0.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::EnsureSQLSchemaVersionedTest",
+ "name" : "@Author::PERLANCAR/EnsureSQLSchemaVersionedTest",
+ "version" : "0.03"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Acme::CPANModules::Blacklist",
+ "name" : "@Author::PERLANCAR/Acme::CPANModules::Blacklist",
+ "version" : "0.001"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Prereqs::EnsureVersion",
+ "name" : "@Author::PERLANCAR/Prereqs::EnsureVersion",
+ "version" : "0.050"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Prereqs::CheckCircular",
+ "name" : "@Author::PERLANCAR/Prereqs::CheckCircular",
+ "version" : "0.006"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::UploadToCPAN::WWWPAUSESimple",
+ "name" : "@Author::PERLANCAR/UploadToCPAN::WWWPAUSESimple",
+ "version" : "0.04"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Prereqs",
+ "config" : {
+ "Dist::Zilla::Plugin::Prereqs" : {
+ "phase" : "test",
+ "type" : "requires"
+ }
+ },
+ "name" : "TestRequires",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Prereqs",
+ "config" : {
+ "Dist::Zilla::Plugin::Prereqs" : {
+ "phase" : "runtime",
+ "type" : "requires"
+ }
+ },
+ "name" : "Prereqs",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::Prereqs",
+ "config" : {
+ "Dist::Zilla::Plugin::Prereqs" : {
+ "phase" : "develop",
+ "type" : "x_spec"
+ }
+ },
+ "name" : "DevelopX_spec",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":InstallModules",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":IncModules",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":TestFiles",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":ExtraTestFiles",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":ExecFiles",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":PerlExecFiles",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":ShareFiles",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":MainModule",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":AllFiles",
+ "version" : "6.012"
+ },
+ {
+ "class" : "Dist::Zilla::Plugin::FinderCode",
+ "name" : ":NoFiles",
+ "version" : "6.012"
+ }
+ ],
+ "zilla" : {
+ "class" : "Dist::Zilla::Dist::Builder",
+ "config" : {
+ "is_trial" : 0
+ },
+ "version" : "6.012"
+ }
+ },
+ "x_authority" : "cpan:PERLANCAR",
+ "x_generated_by_perl" : "v5.28.2",
+ "x_serialization_backend" : "Cpanel::JSON::XS version 4.11",
+ "x_static_install" : 1
+}
+
diff --git a/META.yml b/META.yml
new file mode 100644
index 0000000..3734ee9
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,439 @@
+---
+abstract: 'Various backoff strategies for retry'
+author:
+ - 'perlancar <perlancar@cpan.org>'
+build_requires:
+ File::Spec: '0'
+ IO::Handle: '0'
+ IPC::Open3: '0'
+ Test::Exception: '0'
+ Test::More: '0.98'
+ Test::Number::Delta: '0'
+configure_requires:
+ ExtUtils::MakeMaker: '0'
+dynamic_config: 0
+generated_by: 'Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010'
+license: perl
+meta-spec:
+ url: http://module-build.sourceforge.net/META-spec-v1.4.html
+ version: '1.4'
+name: Algorithm-Backoff
+requires:
+ Time::HiRes: '0'
+ parent: '0'
+ perl: '5.010001'
+ strict: '0'
+ warnings: '0'
+resources:
+ bugtracker: https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff
+ homepage: https://metacpan.org/release/Algorithm-Backoff
+ repository: git://github.com/perlancar/perl-Algorithm-Backoff.git
+version: '0.009'
+x_Dist_Zilla:
+ perl:
+ version: '5.028002'
+ plugins:
+ -
+ class: Dist::Zilla::Plugin::GatherDir
+ config:
+ Dist::Zilla::Plugin::GatherDir:
+ exclude_filename: []
+ exclude_match: []
+ follow_symlinks: 0
+ include_dotfiles: 0
+ prefix: ''
+ prune_directory: []
+ root: .
+ name: '@Author::PERLANCAR/@Filter/GatherDir'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::PruneCruft
+ name: '@Author::PERLANCAR/@Filter/PruneCruft'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::ManifestSkip
+ name: '@Author::PERLANCAR/@Filter/ManifestSkip'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::MetaYAML
+ name: '@Author::PERLANCAR/@Filter/MetaYAML'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::License
+ name: '@Author::PERLANCAR/@Filter/License'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::PodCoverageTests
+ name: '@Author::PERLANCAR/@Filter/PodCoverageTests'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::PodSyntaxTests
+ name: '@Author::PERLANCAR/@Filter/PodSyntaxTests'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::ExtraTests
+ name: '@Author::PERLANCAR/@Filter/ExtraTests'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::ExecDir
+ name: '@Author::PERLANCAR/@Filter/ExecDir'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::ShareDir
+ name: '@Author::PERLANCAR/@Filter/ShareDir'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::MakeMaker
+ config:
+ Dist::Zilla::Role::TestRunner:
+ default_jobs: 1
+ name: '@Author::PERLANCAR/@Filter/MakeMaker'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::Manifest
+ name: '@Author::PERLANCAR/@Filter/Manifest'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::ConfirmRelease
+ name: '@Author::PERLANCAR/@Filter/ConfirmRelease'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::ExecDir
+ name: '@Author::PERLANCAR/ExecDir script'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::PERLANCAR::BeforeBuild
+ name: '@Author::PERLANCAR/PERLANCAR::BeforeBuild'
+ version: '0.596'
+ -
+ class: Dist::Zilla::Plugin::Rinci::AbstractFromMeta
+ name: '@Author::PERLANCAR/Rinci::AbstractFromMeta'
+ version: '0.10'
+ -
+ class: Dist::Zilla::Plugin::PodnameFromFilename
+ name: '@Author::PERLANCAR/PodnameFromFilename'
+ version: '0.02'
+ -
+ class: Dist::Zilla::Plugin::PERLANCAR::EnsurePrereqToSpec
+ name: '@Author::PERLANCAR/PERLANCAR::EnsurePrereqToSpec'
+ version: '0.060'
+ -
+ class: Dist::Zilla::Plugin::PERLANCAR::MetaResources
+ name: '@Author::PERLANCAR/PERLANCAR::MetaResources'
+ version: '0.040'
+ -
+ class: Dist::Zilla::Plugin::CheckChangeLog
+ name: '@Author::PERLANCAR/CheckChangeLog'
+ version: '0.05'
+ -
+ class: Dist::Zilla::Plugin::CheckMetaResources
+ name: '@Author::PERLANCAR/CheckMetaResources'
+ version: '0.001'
+ -
+ class: Dist::Zilla::Plugin::CheckSelfDependency
+ config:
+ Dist::Zilla::Plugin::CheckSelfDependency:
+ finder:
+ - ':InstallModules'
+ Dist::Zilla::Role::ModuleMetadata:
+ Module::Metadata: '1.000033'
+ version: '0.006'
+ name: '@Author::PERLANCAR/CheckSelfDependency'
+ version: '0.011'
+ -
+ class: Dist::Zilla::Plugin::CopyrightYearFromGit
+ name: '@Author::PERLANCAR/CopyrightYearFromGit'
+ version: '0.003'
+ -
+ class: Dist::Zilla::Plugin::IfBuilt
+ name: '@Author::PERLANCAR/IfBuilt'
+ version: '0.02'
+ -
+ class: Dist::Zilla::Plugin::MetaJSON
+ name: '@Author::PERLANCAR/MetaJSON'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::MetaConfig
+ name: '@Author::PERLANCAR/MetaConfig'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::Authority
+ name: '@Author::PERLANCAR/Authority'
+ version: '1.009'
+ -
+ class: Dist::Zilla::Plugin::OurDate
+ name: '@Author::PERLANCAR/OurDate'
+ version: '0.03'
+ -
+ class: Dist::Zilla::Plugin::OurDist
+ name: '@Author::PERLANCAR/OurDist'
+ version: '0.02'
+ -
+ class: Dist::Zilla::Plugin::OurPkgVersion
+ name: '@Author::PERLANCAR/OurPkgVersion'
+ version: '0.15'
+ -
+ class: Dist::Zilla::Plugin::PodWeaver
+ config:
+ Dist::Zilla::Plugin::PodWeaver:
+ finder:
+ - ':InstallModules'
+ - ':ExecFiles'
+ plugins:
+ -
+ class: Pod::Weaver::Plugin::EnsurePod5
+ name: '@CorePrep/EnsurePod5'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Plugin::H1Nester
+ name: '@CorePrep/H1Nester'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Name
+ name: '@Author::PERLANCAR/Name'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Version
+ name: '@Author::PERLANCAR/Version'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Region
+ name: '@Author::PERLANCAR/prelude'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Generic
+ name: SYNOPSIS
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Generic
+ name: DESCRIPTION
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Generic
+ name: OVERVIEW
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Collect
+ name: ATTRIBUTES
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Collect
+ name: METHODS
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Collect
+ name: FUNCTIONS
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Leftovers
+ name: '@Author::PERLANCAR/Leftovers'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Region
+ name: '@Author::PERLANCAR/postlude'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Completion::GetoptLongComplete
+ name: '@Author::PERLANCAR/Completion::GetoptLongComplete'
+ version: '0.08'
+ -
+ class: Pod::Weaver::Section::Completion::GetoptLongSubcommand
+ name: '@Author::PERLANCAR/Completion::GetoptLongSubcommand'
+ version: '0.04'
+ -
+ class: Pod::Weaver::Section::Completion::GetoptLongMore
+ name: '@Author::PERLANCAR/Completion::GetoptLongMore'
+ version: '0.001'
+ -
+ class: Pod::Weaver::Section::Homepage::DefaultCPAN
+ name: '@Author::PERLANCAR/Homepage::DefaultCPAN'
+ version: '0.05'
+ -
+ class: Pod::Weaver::Section::Source::DefaultGitHub
+ name: '@Author::PERLANCAR/Source::DefaultGitHub'
+ version: '0.07'
+ -
+ class: Pod::Weaver::Section::Bugs::DefaultRT
+ name: '@Author::PERLANCAR/Bugs::DefaultRT'
+ version: '0.06'
+ -
+ class: Pod::Weaver::Section::Authors
+ name: '@Author::PERLANCAR/Authors'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Section::Legal
+ name: '@Author::PERLANCAR/Legal'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Plugin::Rinci
+ name: '@Author::PERLANCAR/Rinci'
+ version: '0.780'
+ -
+ class: Pod::Weaver::Plugin::AppendPrepend
+ name: '@Author::PERLANCAR/AppendPrepend'
+ version: '0.01'
+ -
+ class: Pod::Weaver::Plugin::EnsureUniqueSections
+ name: '@Author::PERLANCAR/EnsureUniqueSections'
+ version: '0.163250'
+ -
+ class: Pod::Weaver::Plugin::SingleEncoding
+ name: '@Author::PERLANCAR/SingleEncoding'
+ version: '4.015'
+ -
+ class: Pod::Weaver::Plugin::PERLANCAR::SortSections
+ name: '@Author::PERLANCAR/PERLANCAR::SortSections'
+ version: '0.06'
+ name: '@Author::PERLANCAR/PodWeaver'
+ version: '4.008'
+ -
+ class: Dist::Zilla::Plugin::PruneFiles
+ name: '@Author::PERLANCAR/PruneFiles'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::Pod2Readme
+ name: '@Author::PERLANCAR/Pod2Readme'
+ version: '0.004'
+ -
+ class: Dist::Zilla::Plugin::Rinci::AddPrereqs
+ name: '@Author::PERLANCAR/Rinci::AddPrereqs'
+ version: '0.142'
+ -
+ class: Dist::Zilla::Plugin::Rinci::AddToDb
+ name: '@Author::PERLANCAR/Rinci::AddToDb'
+ version: '0.01'
+ -
+ class: Dist::Zilla::Plugin::Rinci::Validate
+ name: '@Author::PERLANCAR/Rinci::Validate'
+ version: '0.24'
+ -
+ class: Dist::Zilla::Plugin::SetScriptShebang
+ name: '@Author::PERLANCAR/SetScriptShebang'
+ version: '0.01'
+ -
+ class: Dist::Zilla::Plugin::Test::Compile
+ config:
+ Dist::Zilla::Plugin::Test::Compile:
+ bail_out_on_fail: '0'
+ fail_on_warning: author
+ fake_home: 0
+ filename: t/00-compile.t
+ module_finder:
+ - ':InstallModules'
+ needs_display: 0
+ phase: test
+ script_finder:
+ - ':PerlExecFiles'
+ skips: []
+ switch: []
+ name: '@Author::PERLANCAR/Test::Compile'
+ version: '2.058'
+ -
+ class: Dist::Zilla::Plugin::Test::Perl::Critic
+ name: '@Author::PERLANCAR/Test::Perl::Critic'
+ version: '3.001'
+ -
+ class: Dist::Zilla::Plugin::Test::Rinci
+ name: '@Author::PERLANCAR/Test::Rinci'
+ version: '0.040'
+ -
+ class: Dist::Zilla::Plugin::StaticInstall
+ config:
+ Dist::Zilla::Plugin::StaticInstall:
+ dry_run: 0
+ mode: on
+ name: '@Author::PERLANCAR/StaticInstall'
+ version: '0.012'
+ -
+ class: Dist::Zilla::Plugin::EnsureSQLSchemaVersionedTest
+ name: '@Author::PERLANCAR/EnsureSQLSchemaVersionedTest'
+ version: '0.03'
+ -
+ class: Dist::Zilla::Plugin::Acme::CPANModules::Blacklist
+ name: '@Author::PERLANCAR/Acme::CPANModules::Blacklist'
+ version: '0.001'
+ -
+ class: Dist::Zilla::Plugin::Prereqs::EnsureVersion
+ name: '@Author::PERLANCAR/Prereqs::EnsureVersion'
+ version: '0.050'
+ -
+ class: Dist::Zilla::Plugin::Prereqs::CheckCircular
+ name: '@Author::PERLANCAR/Prereqs::CheckCircular'
+ version: '0.006'
+ -
+ class: Dist::Zilla::Plugin::UploadToCPAN::WWWPAUSESimple
+ name: '@Author::PERLANCAR/UploadToCPAN::WWWPAUSESimple'
+ version: '0.04'
+ -
+ class: Dist::Zilla::Plugin::Prereqs
+ config:
+ Dist::Zilla::Plugin::Prereqs:
+ phase: test
+ type: requires
+ name: TestRequires
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::Prereqs
+ config:
+ Dist::Zilla::Plugin::Prereqs:
+ phase: runtime
+ type: requires
+ name: Prereqs
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::Prereqs
+ config:
+ Dist::Zilla::Plugin::Prereqs:
+ phase: develop
+ type: x_spec
+ name: DevelopX_spec
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':InstallModules'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':IncModules'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':TestFiles'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':ExtraTestFiles'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':ExecFiles'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':PerlExecFiles'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':ShareFiles'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':MainModule'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':AllFiles'
+ version: '6.012'
+ -
+ class: Dist::Zilla::Plugin::FinderCode
+ name: ':NoFiles'
+ version: '6.012'
+ zilla:
+ class: Dist::Zilla::Dist::Builder
+ config:
+ is_trial: '0'
+ version: '6.012'
+x_authority: cpan:PERLANCAR
+x_generated_by_perl: v5.28.2
+x_serialization_backend: 'YAML::Tiny version 1.73'
+x_static_install: 1
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..a91d5b0
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,63 @@
+# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.012.
+use strict;
+use warnings;
+
+use 5.010001;
+
+use ExtUtils::MakeMaker;
+
+my %WriteMakefileArgs = (
+ "ABSTRACT" => "Various backoff strategies for retry",
+ "AUTHOR" => "perlancar <perlancar\@cpan.org>",
+ "CONFIGURE_REQUIRES" => {
+ "ExtUtils::MakeMaker" => 0
+ },
+ "DISTNAME" => "Algorithm-Backoff",
+ "LICENSE" => "perl",
+ "MIN_PERL_VERSION" => "5.010001",
+ "NAME" => "Algorithm::Backoff",
+ "PREREQ_PM" => {
+ "Time::HiRes" => 0,
+ "parent" => 0,
+ "strict" => 0,
+ "warnings" => 0
+ },
+ "TEST_REQUIRES" => {
+ "File::Spec" => 0,
+ "IO::Handle" => 0,
+ "IPC::Open3" => 0,
+ "Test::Exception" => 0,
+ "Test::More" => "0.98",
+ "Test::Number::Delta" => 0
+ },
+ "VERSION" => "0.009",
+ "test" => {
+ "TESTS" => "t/*.t"
+ }
+);
+
+
+my %FallbackPrereqs = (
+ "File::Spec" => 0,
+ "IO::Handle" => 0,
+ "IPC::Open3" => 0,
+ "Test::Exception" => 0,
+ "Test::More" => "0.98",
+ "Test::Number::Delta" => 0,
+ "Time::HiRes" => 0,
+ "parent" => 0,
+ "strict" => 0,
+ "warnings" => 0
+);
+
+
+unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) {
+ delete $WriteMakefileArgs{TEST_REQUIRES};
+ delete $WriteMakefileArgs{BUILD_REQUIRES};
+ $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs;
+}
+
+delete $WriteMakefileArgs{CONFIGURE_REQUIRES}
+ unless eval { ExtUtils::MakeMaker->VERSION(6.52) };
+
+WriteMakefile(%WriteMakefileArgs);
diff --git a/README b/README
new file mode 100644
index 0000000..be7350c
--- /dev/null
+++ b/README
@@ -0,0 +1,120 @@
+NAME
+ Algorithm::Backoff - Various backoff strategies for retry
+
+VERSION
+ This document describes version 0.009 of Algorithm::Backoff (from Perl
+ distribution Algorithm-Backoff), released on 2019-06-20.
+
+SYNOPSIS
+ # 1. pick a strategy and instantiate
+
+ use Algorithm::Backoff::Constant;
+ my $ab = Algorithm::Backoff::Constant->new(
+ delay => 2, # required
+ #delay_on_success => 0, # optional, default 0
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional but must be monotonically increasing.
+
+ my $secs = $ab->failure(); # => 2
+ my $secs = $ab->success(); # => 0
+ my $secs = $ab->failure(); # => 2
+
+DESCRIPTION
+ This distribution provides several classes that implement various
+ backoff strategies for setting delay between retry attempts.
+
+ This class ("Algorithm::Backoff") is a base class only.
+
+METHODS
+ new
+ Usage:
+
+ new(%args) -> obj
+
+ This function is not exported.
+
+ Arguments ('*' denotes required arguments):
+
+ * jitter_factor => *float*
+
+ How much to add randomness.
+
+ If you set this to a value larger than 0, the actual delay will be
+ between a random number between original_delay * (1-jitter_factor)
+ and original_delay * (1+jitter_factor). Jitters are usually added to
+ avoid so-called "thundering herd" problem.
+
+ The jitter will be applied to delay on failure as well as on
+ success.
+
+ * max_attempts => *uint* (default: 0)
+
+ Maximum number consecutive failures before giving up.
+
+ 0 means to retry endlessly without ever giving up. 1 means to give
+ up after a single failure (i.e. no retry attempts). 2 means to retry
+ once after a failure. Note that after a success, the number of
+ attempts is reset (as expected). So if max_attempts is 3, and if you
+ fail twice then succeed, then on the next failure the algorithm will
+ retry again for a maximum of 3 times.
+
+ Return value: (obj)
+
+ success
+ Usage:
+
+ my $secs = $obj->success([ $timestamp ]);
+
+ Log a successful attempt. If not specified, $timestamp defaults to
+ current time. Will return the suggested number of seconds to wait before
+ doing another attempt.
+
+ failure
+ Usage:
+
+ my $secs = $obj->failure([ $timestamp ]);
+
+ Log a failed attempt. If not specified, $timestamp defaults to current
+ time. Will return the suggested number of seconds to wait before doing
+ another attempt, or -1 if it suggests that one gives up (e.g. if
+ "max_attempts" parameter has been exceeded).
+
+HOMEPAGE
+ Please visit the project's homepage at
+ <https://metacpan.org/release/Algorithm-Backoff>.
+
+SOURCE
+ Source repository is at
+ <https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+BUGS
+ Please report any bugs or feature requests on the bugtracker website
+ <https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+ When submitting a bug or request, please include a test-file or a patch
+ to an existing test-file that illustrates the bug or desired feature.
+
+SEE ALSO
+ Retry::Backoff - an application of Algorithm::Backoff to retry a piece
+ of code using various backoff strategies.
+
+ App::AlgorithmBackoffUtils - various CLI's related to
+ Algorithm::Backoff.
+
+ Action::Retry - Somehow I didn't find this module before writing
+ Algorithm::Backoff. Otherwise I probably would not have created
+ Algorithm::Backoff. But Algorithm::Backoff offers an alternative
+ interface, some additional parameters (like delay on success and jitter
+ factor), a lighter footprint (no Moo), and a couple more strategies.
+
+AUTHOR
+ perlancar <perlancar@cpan.org>
+
+COPYRIGHT AND LICENSE
+ This software is copyright (c) 2019 by perlancar@cpan.org.
+
+ This is free software; you can redistribute it and/or modify it under
+ the same terms as the Perl 5 programming language system itself.
+
diff --git a/dist.ini b/dist.ini
new file mode 100644
index 0000000..22d80ac
--- /dev/null
+++ b/dist.ini
@@ -0,0 +1,23 @@
+version = 0.009
+
+name = Algorithm-Backoff
+
+[@Author::PERLANCAR]
+:version=0.594
+
+[Prereqs / TestRequires]
+Test::Exception=0
+Test::More=0.98
+Test::Number::Delta=0
+
+[Prereqs]
+perl=5.010001
+parent=0
+strict=0
+warnings=0
+Time::HiRes=0
+
+[Prereqs / DevelopX_spec]
+-phase=develop
+-relationship=x_spec
+Rinci=1.1.90
diff --git a/lib/Algorithm/Backoff.pm b/lib/Algorithm/Backoff.pm
new file mode 100644
index 0000000..0804f7d
--- /dev/null
+++ b/lib/Algorithm/Backoff.pm
@@ -0,0 +1,410 @@
+package Algorithm::Backoff;
+
+our $DATE = '2019-06-20'; # DATE
+our $VERSION = '0.009'; # VERSION
+
+use 5.010001;
+use strict 'subs', 'vars';
+use warnings;
+
+use Time::HiRes qw(time);
+
+our %SPEC;
+
+our %attr_consider_actual_delay = (
+ consider_actual_delay => {
+ summary => 'Whether to consider actual delay',
+ schema => ['bool*'],
+ default => 0,
+ tags => ['common'],
+ description => <<'_',
+
+If set to true, will take into account the actual delay (timestamp difference).
+For example, when using the Constant strategy of delay=2, you log failure()
+again right after the previous failure() (i.e. specify the same timestamp).
+failure() will then return ~2+2 = 4 seconds. On the other hand, if you waited 2
+seconds before calling failure() again (i.e. specify the timestamp that is 2
+seconds larger than the previous timestamp), failure() will return 2 seconds.
+And if you waited 4 seconds or more, failure() will return 0.
+
+_
+ },
+);
+
+our %attr_max_actual_duration = (
+ max_actual_duration => {
+ summary => 'Maximum number of seconds for all of the attempts (0 means unlimited)',
+ schema => ['ufloat*'],
+ default => 0,
+ tags => ['common'],
+ description => <<'_',
+
+If set to a positive number, will limit the number of seconds for all of the
+attempts. This setting is used to limit the amount of time you are willing to
+spend on a task. For example, when using the Exponential strategy of
+initial_delay=3 and max_attempts=10, the delays will be 3, 6, 12, 24, ... If
+failures are logged according to the suggested delays, and max_actual_duration
+is set to 21 seconds, then the third failure() will return -1 instead of 24
+because 3+6+12 >= 21, even though max_attempts has not been exceeded.
+
+_
+ },
+);
+
+our %attr_max_attempts = (
+ max_attempts => {
+ summary => 'Maximum number consecutive failures before giving up',
+ schema => 'uint*',
+ default => 0,
+ tags => ['common'],
+ description => <<'_',
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+_
+ },
+);
+
+our %attr_jitter_factor = (
+ jitter_factor => {
+ summary => 'How much to add randomness',
+ schema => ['float*', between=>[0, 0.5]],
+ tags => ['common'],
+ description => <<'_',
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+_
+ },
+);
+
+our %attr_delay_on_success = (
+ delay_on_success => {
+ summary => 'Number of seconds to wait after a success',
+ schema => 'ufloat*',
+ default => 0,
+ },
+);
+
+our %attr_max_delay = (
+ max_delay => {
+ summary => 'Maximum delay time, in seconds',
+ schema => 'ufloat*',
+ tags => ['common'],
+ },
+);
+
+our %attr_min_delay = (
+ min_delay => {
+ summary => 'Maximum delay time, in seconds',
+ schema => 'ufloat*',
+ default => 0,
+ tags => ['common'],
+ },
+);
+
+our %attr_initial_delay = (
+ initial_delay => {
+ summary => 'Initial delay for the first attempt after failure, '.
+ 'in seconds',
+ schema => 'ufloat*',
+ req => 1,
+ },
+);
+
+our %attr_delay_multiple_on_failure = (
+ delay_multiple_on_failure => {
+ summary => 'How much to multiple previous delay, upon failure (e.g. 1.5)',
+ schema => 'ufloat*',
+ req => 1,
+ },
+);
+
+our %attr_delay_multiple_on_success = (
+ delay_multiple_on_success => {
+ summary => 'How much to multiple previous delay, upon success (e.g. 0.5)',
+ schema => 'ufloat*',
+ req => 1,
+ },
+);
+
+our %attr_delay_increment_on_failure = (
+ delay_increment_on_failure => {
+ summary => 'How much to add to previous delay, in seconds, upon failure (e.g. 5)',
+ schema => 'float*',
+ req => 1,
+ },
+);
+
+our %attr_delay_increment_on_success = (
+ delay_increment_on_success => {
+ summary => 'How much to add to previous delay, in seconds, upon success (e.g. -5)',
+ schema => 'float*',
+ req => 1,
+ },
+);
+
+$SPEC{new} = {
+ v => 1.1,
+ is_class_meth => 1,
+ is_func => 0,
+ args => {
+ %attr_max_attempts,
+ %attr_jitter_factor,
+ },
+ result_naked => 1,
+ result => {
+ schema => 'obj*',
+ },
+};
+sub new {
+ my ($class, %args) = @_;
+
+ my $attrspec = ${"$class\::SPEC"}{new}{args};
+
+ # check known attributes
+ for my $arg (keys %args) {
+ $arg =~ /\A(_start_timestamp)\z/ and next;
+ $attrspec->{$arg} or die "$class: Unknown attribute '$arg'";
+ }
+ # check required attributes and set default
+ for my $attr (keys %$attrspec) {
+ if ($attrspec->{$attr}{req}) {
+ exists($args{$attr})
+ or die "$class: Missing required attribute '$attr'";
+ }
+ if (exists $attrspec->{$attr}{default}) {
+ $args{$attr} //= $attrspec->{$attr}{default};
+ }
+ }
+ $args{_attempts} = 0;
+ $args{_start_timestamp} //= time();
+ bless \%args, $class;
+}
+
+sub _consider_actual_delay {
+ my ($self, $delay, $timestamp) = @_;
+
+ $self->{_last_delay} //= 0;
+ my $actual_delay = $timestamp - $self->{_last_timestamp};
+ my $new_delay = $delay + $self->{_last_delay} - $actual_delay;
+ $self->{_last_delay} = $new_delay;
+ $new_delay;
+}
+
+sub _add_jitter {
+ my ($self, $delay) = @_;
+ return $delay unless $delay && $self->{jitter_factor};
+ my $min = $delay * (1-$self->{jitter_factor});
+ my $max = $delay * (1+$self->{jitter_factor});
+ $min + ($max-$min)*rand();
+}
+
+sub _success_or_failure {
+ my ($self, $is_success, $timestamp) = @_;
+
+ $self->{_last_timestamp} //= $timestamp;
+ $timestamp >= $self->{_last_timestamp} or
+ die ref($self).": Decreasing timestamp ".
+ "($self->{_last_timestamp} -> $timestamp)";
+
+ my $delay = $is_success ?
+ $self->_success($timestamp) : $self->_failure($timestamp);
+
+ $delay = $self->_consider_actual_delay($delay, $timestamp)
+ if $self->{consider_actual_delay};
+
+ $delay = $self->_add_jitter($delay)
+ if $self->{jitter_factor};
+
+ # keep between max(0, min_delay) and max_delay
+ $delay = $self->{max_delay}
+ if defined $self->{max_delay} && $delay > $self->{max_delay};
+ $delay = 0 if $delay < 0;
+ $delay = $self->{min_delay}
+ if defined $self->{min_delay} && $delay < $self->{min_delay};
+
+ $self->{_last_timestamp} = $timestamp;
+ $self->{_prev_delay} = $delay;
+ $delay;
+}
+
+sub success {
+ my ($self, $timestamp) = @_;
+
+ $timestamp //= time();
+
+ $self->{_attempts} = 0;
+
+ $self->_success_or_failure(1, $timestamp);
+}
+
+sub failure {
+ my ($self, $timestamp) = @_;
+
+ $timestamp //= time();
+
+ return -1 if defined $self->{max_actual_duration} &&
+ $self->{max_actual_duration} > 0 &&
+ $timestamp - $self->{_start_timestamp} >= $self->{max_actual_duration};
+
+ $self->{_attempts}++;
+ return -1 if $self->{max_attempts} &&
+ $self->{_attempts} >= $self->{max_attempts};
+
+ $self->_success_or_failure(0, $timestamp);
+}
+
+1;
+# ABSTRACT: Various backoff strategies for retry
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Algorithm::Backoff - Various backoff strategies for retry
+
+=head1 VERSION
+
+This document describes version 0.009 of Algorithm::Backoff (from Perl distribution Algorithm-Backoff), released on 2019-06-20.
+
+=head1 SYNOPSIS
+
+ # 1. pick a strategy and instantiate
+
+ use Algorithm::Backoff::Constant;
+ my $ab = Algorithm::Backoff::Constant->new(
+ delay => 2, # required
+ #delay_on_success => 0, # optional, default 0
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional but must be monotonically increasing.
+
+ my $secs = $ab->failure(); # => 2
+ my $secs = $ab->success(); # => 0
+ my $secs = $ab->failure(); # => 2
+
+=head1 DESCRIPTION
+
+This distribution provides several classes that implement various backoff
+strategies for setting delay between retry attempts.
+
+This class (C<Algorithm::Backoff>) is a base class only.
+
+=head1 METHODS
+
+
+=head2 new
+
+Usage:
+
+ new(%args) -> obj
+
+This function is not exported.
+
+Arguments ('*' denotes required arguments):
+
+=over 4
+
+=item * B<jitter_factor> => I<float>
+
+How much to add randomness.
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+=item * B<max_attempts> => I<uint> (default: 0)
+
+Maximum number consecutive failures before giving up.
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+=back
+
+Return value: (obj)
+
+
+=head2 success
+
+Usage:
+
+ my $secs = $obj->success([ $timestamp ]);
+
+Log a successful attempt. If not specified, C<$timestamp> defaults to current
+time. Will return the suggested number of seconds to wait before doing another
+attempt.
+
+=head2 failure
+
+Usage:
+
+ my $secs = $obj->failure([ $timestamp ]);
+
+Log a failed attempt. If not specified, C<$timestamp> defaults to current time.
+Will return the suggested number of seconds to wait before doing another
+attempt, or -1 if it suggests that one gives up (e.g. if C<max_attempts>
+parameter has been exceeded).
+
+=head1 HOMEPAGE
+
+Please visit the project's homepage at L<https://metacpan.org/release/Algorithm-Backoff>.
+
+=head1 SOURCE
+
+Source repository is at L<https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+=head1 BUGS
+
+Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+When submitting a bug or request, please include a test-file or a
+patch to an existing test-file that illustrates the bug or desired
+feature.
+
+=head1 SEE ALSO
+
+L<Retry::Backoff> - an application of Algorithm::Backoff to retry a piece of
+code using various backoff strategies.
+
+L<App::AlgorithmBackoffUtils> - various CLI's related to Algorithm::Backoff.
+
+L<Action::Retry> - Somehow I didn't find this module before writing
+Algorithm::Backoff. Otherwise I probably would not have created
+Algorithm::Backoff. But Algorithm::Backoff offers an alternative interface, some
+additional parameters (like delay on success and jitter factor), a lighter
+footprint (no Moo), and a couple more strategies.
+
+=head1 AUTHOR
+
+perlancar <perlancar@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/lib/Algorithm/Backoff/Constant.pm b/lib/Algorithm/Backoff/Constant.pm
new file mode 100644
index 0000000..753ef4c
--- /dev/null
+++ b/lib/Algorithm/Backoff/Constant.pm
@@ -0,0 +1,221 @@
+package Algorithm::Backoff::Constant;
+
+our $DATE = '2019-06-20'; # DATE
+our $VERSION = '0.009'; # VERSION
+
+use strict;
+use warnings;
+
+use parent qw(Algorithm::Backoff);
+
+our %SPEC;
+
+$SPEC{new} = {
+ v => 1.1,
+ is_class_meth => 1,
+ is_func => 0,
+ args => {
+ %Algorithm::Backoff::attr_consider_actual_delay,
+ %Algorithm::Backoff::attr_max_actual_duration,
+ %Algorithm::Backoff::attr_max_attempts,
+ %Algorithm::Backoff::attr_jitter_factor,
+ %Algorithm::Backoff::attr_delay_on_success,
+ %Algorithm::Backoff::attr_min_delay,
+ %Algorithm::Backoff::attr_max_delay,
+ delay => {
+ summary => 'Number of seconds to wait after a failure',
+ schema => 'ufloat*',
+ req => 1,
+ },
+ },
+ result_naked => 1,
+ result => {
+ schema => 'obj*',
+ },
+};
+
+sub _success {
+ my ($self, $timestamp) = @_;
+ $self->{delay_on_success};
+}
+
+sub _failure {
+ my ($self, $timestamp) = @_;
+ $self->{delay};
+}
+
+1;
+# ABSTRACT: Backoff using a constant delay
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Algorithm::Backoff::Constant - Backoff using a constant delay
+
+=head1 VERSION
+
+This document describes version 0.009 of Algorithm::Backoff::Constant (from Perl distribution Algorithm-Backoff), released on 2019-06-20.
+
+=head1 SYNOPSIS
+
+ use Algorithm::Backoff::Constant;
+
+ # 1. instantiate
+
+ my $ab = Algorithm::Backoff::Constant->new(
+ #consider_actual_delay => 1, # optional, default 0
+ #max_actual_duration => 0, # optional, default 0 (retry endlessly)
+ #max_attempts => 0, # optional, default 0 (retry endlessly)
+ #jitter_factor => 0, # optional, set to positive value to add randomness
+ delay => 2, # required
+ #delay_on_success => 0, # optional, default 0
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional argument (default is current time) but must be monotonically
+ # increasing.
+
+ my $secs = $ab->failure(1554652553); # => 2
+ my $secs = $ab->success(); # => 0
+ my $secs = $ab->failure(); # => 2
+
+Illustration using CLI L<show-backoff-delays> (5 failures followed by 3
+successes):
+
+ % show-backoff-delays -a Constant --delay 2 \
+ 0 0 0 0 0 1 1 1
+ 2
+ 2
+ 2
+ 2
+ 2
+ 0
+ 0
+ 0
+
+=head1 DESCRIPTION
+
+This backoff strategy is one of the simplest: it waits X second(s) after each
+failure, or Y second(s) (default 0) after a success. There are limits on the
+number of attempts (`max_attempts`) and total duration (`max_actual_duration`).
+Some randomness can be introduced to avoid "thundering herd problem".
+
+=head1 METHODS
+
+
+=head2 new
+
+Usage:
+
+ new(%args) -> obj
+
+This function is not exported.
+
+Arguments ('*' denotes required arguments):
+
+=over 4
+
+=item * B<consider_actual_delay> => I<bool> (default: 0)
+
+Whether to consider actual delay.
+
+If set to true, will take into account the actual delay (timestamp difference).
+For example, when using the Constant strategy of delay=2, you log failure()
+again right after the previous failure() (i.e. specify the same timestamp).
+failure() will then return ~2+2 = 4 seconds. On the other hand, if you waited 2
+seconds before calling failure() again (i.e. specify the timestamp that is 2
+seconds larger than the previous timestamp), failure() will return 2 seconds.
+And if you waited 4 seconds or more, failure() will return 0.
+
+=item * B<delay>* => I<ufloat>
+
+Number of seconds to wait after a failure.
+
+=item * B<delay_on_success> => I<ufloat> (default: 0)
+
+Number of seconds to wait after a success.
+
+=item * B<jitter_factor> => I<float>
+
+How much to add randomness.
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+=item * B<max_actual_duration> => I<ufloat> (default: 0)
+
+Maximum number of seconds for all of the attempts (0 means unlimited).
+
+If set to a positive number, will limit the number of seconds for all of the
+attempts. This setting is used to limit the amount of time you are willing to
+spend on a task. For example, when using the Exponential strategy of
+initial_delay=3 and max_attempts=10, the delays will be 3, 6, 12, 24, ... If
+failures are logged according to the suggested delays, and max_actual_duration
+is set to 21 seconds, then the third failure() will return -1 instead of 24
+because 3+6+12 >= 21, even though max_attempts has not been exceeded.
+
+=item * B<max_attempts> => I<uint> (default: 0)
+
+Maximum number consecutive failures before giving up.
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+=item * B<max_delay> => I<ufloat>
+
+Maximum delay time, in seconds.
+
+=item * B<min_delay> => I<ufloat> (default: 0)
+
+Maximum delay time, in seconds.
+
+=back
+
+Return value: (obj)
+
+=head1 HOMEPAGE
+
+Please visit the project's homepage at L<https://metacpan.org/release/Algorithm-Backoff>.
+
+=head1 SOURCE
+
+Source repository is at L<https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+=head1 BUGS
+
+Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+When submitting a bug or request, please include a test-file or a
+patch to an existing test-file that illustrates the bug or desired
+feature.
+
+=head1 SEE ALSO
+
+L<Algorithm::Backoff>
+
+Other C<Algorithm::Backoff::*> classes.
+
+=head1 AUTHOR
+
+perlancar <perlancar@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/lib/Algorithm/Backoff/Exponential.pm b/lib/Algorithm/Backoff/Exponential.pm
new file mode 100644
index 0000000..b482aa8
--- /dev/null
+++ b/lib/Algorithm/Backoff/Exponential.pm
@@ -0,0 +1,249 @@
+package Algorithm::Backoff::Exponential;
+
+our $DATE = '2019-06-20'; # DATE
+our $VERSION = '0.009'; # VERSION
+
+use strict;
+use warnings;
+
+use parent qw(Algorithm::Backoff);
+
+our %SPEC;
+
+$SPEC{new} = {
+ v => 1.1,
+ is_class_meth => 1,
+ is_func => 0,
+ args => {
+ %Algorithm::Backoff::attr_consider_actual_delay,
+ %Algorithm::Backoff::attr_max_actual_duration,
+ %Algorithm::Backoff::attr_max_attempts,
+ %Algorithm::Backoff::attr_jitter_factor,
+ %Algorithm::Backoff::attr_delay_on_success,
+ %Algorithm::Backoff::attr_min_delay,
+ %Algorithm::Backoff::attr_max_delay,
+ %Algorithm::Backoff::attr_initial_delay,
+ exponent_base => {
+ schema => 'ufloat*',
+ default => 2,
+ },
+ },
+ result_naked => 1,
+ result => {
+ schema => 'obj*',
+ },
+};
+
+sub _success {
+ my ($self, $timestamp) = @_;
+ $self->{delay_on_success};
+}
+
+sub _failure {
+ my ($self, $timestamp) = @_;
+ my $delay = $self->{initial_delay} *
+ $self->{exponent_base} ** ($self->{_attempts}-1);
+}
+
+1;
+# ABSTRACT: Backoff exponentially
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Algorithm::Backoff::Exponential - Backoff exponentially
+
+=head1 VERSION
+
+This document describes version 0.009 of Algorithm::Backoff::Exponential (from Perl distribution Algorithm-Backoff), released on 2019-06-20.
+
+=head1 SYNOPSIS
+
+ use Algorithm::Backoff::Exponential;
+
+ # 1. instantiate
+
+ my $ab = Algorithm::Backoff::Exponential->new(
+ #consider_actual_delay => 1, # optional, default 0
+ #max_actual_duration => 0, # optional, default 0 (retry endlessly)
+ #max_attempts => 0, # optional, default 0 (retry endlessly)
+ #jitter_factor => 0.25, # optional, default 0
+ initial_delay => 5, # required
+ #max_delay => 100, # optional
+ #exponent_base => 2, # optional, default 2 (binary exponentiation)
+ #delay_on_success => 0, # optional, default 0
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional but must be monotonically increasing.
+
+ # for example, using the parameters initial_delay=5, max_delay=100:
+
+ my $secs;
+ $secs = $ab->failure(); # => 5 (= initial_delay)
+ $secs = $ab->failure(); # => 10 (5 * 2^1)
+ $secs = $ab->failure(); # => 20 (5 * 2^2)
+ $secs = $ab->failure(); # => 33 (5 * 2^3 - 7)
+ $secs = $ab->failure(); # => 80 (5 * 2^4)
+ $secs = $ab->failure(); # => 100 ( min(5 * 2^5, 100) )
+ $secs = $ab->success(); # => 0 (= delay_on_success)
+
+Illustration using CLI L<show-backoff-delays> (10 failures followed by 3
+successes):
+
+ % show-backoff-delays -a Exponential --initial-delay 1 --max-delay 200 \
+ 0 0 0 0 0 0 0 0 0 0 1 1 1
+ 1
+ 2
+ 4
+ 8
+ 16
+ 32
+ 64
+ 128
+ 200
+ 200
+ 0
+ 0
+ 0
+
+=head1 DESCRIPTION
+
+This backoff algorithm calculates the next delay as:
+
+ initial_delay * exponent_base ** (attempts-1)
+
+Only the C<initial_delay> is required. C<exponent_base> is 2 by default (binary
+exponential). For the first failure attempt (C<attempts> = 1) the delay equals
+the initial delay. Then it is doubled, quadrupled, and so on (using the default
+exponent base of 2).
+
+There are limits on the number of attempts (`max_attempts`) and total duration
+(`max_actual_duration`).
+
+It is recommended to add a jitter factor, e.g. 0.25 to add some randomness to
+avoid "thundering herd problem".
+
+=head1 METHODS
+
+
+=head2 new
+
+Usage:
+
+ new(%args) -> obj
+
+This function is not exported.
+
+Arguments ('*' denotes required arguments):
+
+=over 4
+
+=item * B<consider_actual_delay> => I<bool> (default: 0)
+
+Whether to consider actual delay.
+
+If set to true, will take into account the actual delay (timestamp difference).
+For example, when using the Constant strategy of delay=2, you log failure()
+again right after the previous failure() (i.e. specify the same timestamp).
+failure() will then return ~2+2 = 4 seconds. On the other hand, if you waited 2
+seconds before calling failure() again (i.e. specify the timestamp that is 2
+seconds larger than the previous timestamp), failure() will return 2 seconds.
+And if you waited 4 seconds or more, failure() will return 0.
+
+=item * B<delay_on_success> => I<ufloat> (default: 0)
+
+Number of seconds to wait after a success.
+
+=item * B<exponent_base> => I<ufloat> (default: 2)
+
+=item * B<initial_delay>* => I<ufloat>
+
+Initial delay for the first attempt after failure, in seconds.
+
+=item * B<jitter_factor> => I<float>
+
+How much to add randomness.
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+=item * B<max_actual_duration> => I<ufloat> (default: 0)
+
+Maximum number of seconds for all of the attempts (0 means unlimited).
+
+If set to a positive number, will limit the number of seconds for all of the
+attempts. This setting is used to limit the amount of time you are willing to
+spend on a task. For example, when using the Exponential strategy of
+initial_delay=3 and max_attempts=10, the delays will be 3, 6, 12, 24, ... If
+failures are logged according to the suggested delays, and max_actual_duration
+is set to 21 seconds, then the third failure() will return -1 instead of 24
+because 3+6+12 >= 21, even though max_attempts has not been exceeded.
+
+=item * B<max_attempts> => I<uint> (default: 0)
+
+Maximum number consecutive failures before giving up.
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+=item * B<max_delay> => I<ufloat>
+
+Maximum delay time, in seconds.
+
+=item * B<min_delay> => I<ufloat> (default: 0)
+
+Maximum delay time, in seconds.
+
+=back
+
+Return value: (obj)
+
+=head1 HOMEPAGE
+
+Please visit the project's homepage at L<https://metacpan.org/release/Algorithm-Backoff>.
+
+=head1 SOURCE
+
+Source repository is at L<https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+=head1 BUGS
+
+Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+When submitting a bug or request, please include a test-file or a
+patch to an existing test-file that illustrates the bug or desired
+feature.
+
+=head1 SEE ALSO
+
+L<https://en.wikipedia.org/wiki/Exponential_backoff>
+
+L<Algorithm::Backoff>
+
+Other C<Algorithm::Backoff::*> classes.
+
+=head1 AUTHOR
+
+perlancar <perlancar@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/lib/Algorithm/Backoff/Fibonacci.pm b/lib/Algorithm/Backoff/Fibonacci.pm
new file mode 100644
index 0000000..b93384a
--- /dev/null
+++ b/lib/Algorithm/Backoff/Fibonacci.pm
@@ -0,0 +1,267 @@
+package Algorithm::Backoff::Fibonacci;
+
+our $DATE = '2019-06-20'; # DATE
+our $VERSION = '0.009'; # VERSION
+
+use strict;
+use warnings;
+
+use parent qw(Algorithm::Backoff);
+
+our %SPEC;
+
+$SPEC{new} = {
+ v => 1.1,
+ is_class_meth => 1,
+ is_func => 0,
+ args => {
+ %Algorithm::Backoff::attr_consider_actual_delay,
+ %Algorithm::Backoff::attr_max_actual_duration,
+ %Algorithm::Backoff::attr_max_attempts,
+ %Algorithm::Backoff::attr_jitter_factor,
+ %Algorithm::Backoff::attr_delay_on_success,
+ %Algorithm::Backoff::attr_min_delay,
+ %Algorithm::Backoff::attr_max_delay,
+ initial_delay1 => {
+ summary => 'Initial delay for the first attempt after failure, '.
+ 'in seconds',
+ schema => 'ufloat*',
+ req => 1,
+ },
+ initial_delay2 => {
+ summary => 'Initial delay for the second attempt after failure, '.
+ 'in seconds',
+ schema => 'ufloat*',
+ req => 1,
+ },
+ },
+ result_naked => 1,
+ result => {
+ schema => 'obj*',
+ },
+};
+
+sub _success {
+ my ($self, $timestamp) = @_;
+ $self->{delay_on_success};
+}
+
+sub _failure {
+ my ($self, $timestamp) = @_;
+ if ($self->{_attempts} == 1) {
+ $self->{_delay_n_min_1} = 0;
+ $self->{_delay_n} = $self->{initial_delay1};
+ } elsif ($self->{_attempts} == 2) {
+ $self->{_delay_n_min_1} = $self->{initial_delay1};
+ $self->{_delay_n} = $self->{initial_delay2};
+ } else {
+ my $tmp = $self->{_delay_n};
+ $self->{_delay_n} = $self->{_delay_n_min_1} + $self->{_delay_n};
+ $self->{_delay_n_min_1} = $tmp;
+ $self->{_delay_n};
+ }
+}
+
+1;
+# ABSTRACT: Backoff using Fibonacci sequence
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Algorithm::Backoff::Fibonacci - Backoff using Fibonacci sequence
+
+=head1 VERSION
+
+This document describes version 0.009 of Algorithm::Backoff::Fibonacci (from Perl distribution Algorithm-Backoff), released on 2019-06-20.
+
+=head1 SYNOPSIS
+
+ use Algorithm::Backoff::Fibonacci;
+
+ # 1. instantiate
+
+ my $ab = Algorithm::Backoff::Fibonacci->new(
+ #consider_actual_delay => 1, # optional, default 0
+ #max_actual_duration => 0, # optional, default 0 (retry endlessly)
+ #max_attempts => 0, # optional, default 0 (retry endlessly)
+ #jitter_factor => 0.25, # optional, default 0
+ initial_delay1 => 2, # required
+ initial_delay2 => 3, # required
+ #max_delay => 20, # optional
+ #delay_on_success => 0, # optional, default 0
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional but must be monotonically increasing.
+
+ my $secs;
+ $secs = $ab->failure(); # => 2 (= initial_delay1)
+ $secs = $ab->failure(); # => 3 (= initial_delay2)
+ $secs = $ab->failure(); # => 5 (= 2+3)
+ $secs = $ab->failure(); # => 8 (= 3+5)
+ sleep 1;
+ $secs = $ab->failure(); # => 12 (= 5+8 -1)
+ $secs = $ab->failure(); # => 20 (= min(13+8, 20) = max_delay)
+
+ $secs = $ab->success(); # => 0 (= delay_on_success)
+
+Illustration using CLI L<show-backoff-delays> (10 failures followed by 3
+successes):
+
+ % show-backoff-delays -a Fibonacci --initial-delay1 0 --initial-delay2 1 \
+ 0 0 0 0 0 0 0 0 0 0 1 1 1
+ 0
+ 1
+ 1
+ 2
+ 3
+ 5
+ 8
+ 13
+ 21
+ 34
+ 0
+ 0
+ 0
+
+=head1 DESCRIPTION
+
+This backoff algorithm calculates the next delay using Fibonacci sequence. For
+example, if the two initial numbers are 2 and 3:
+
+ 2, 3, 5, 8, 13, 21, ...
+
+C<initial_delay1> and C<initial_delay2> are required. The other attributes are
+optional.
+
+There are limits on the number of attempts (`max_attempts`) and total duration
+(`max_actual_duration`).
+
+It is recommended to add a jitter factor, e.g. 0.25 to add some randomness to
+avoid "thundering herd problem".
+
+=head1 METHODS
+
+
+=head2 new
+
+Usage:
+
+ new(%args) -> obj
+
+This function is not exported.
+
+Arguments ('*' denotes required arguments):
+
+=over 4
+
+=item * B<consider_actual_delay> => I<bool> (default: 0)
+
+Whether to consider actual delay.
+
+If set to true, will take into account the actual delay (timestamp difference).
+For example, when using the Constant strategy of delay=2, you log failure()
+again right after the previous failure() (i.e. specify the same timestamp).
+failure() will then return ~2+2 = 4 seconds. On the other hand, if you waited 2
+seconds before calling failure() again (i.e. specify the timestamp that is 2
+seconds larger than the previous timestamp), failure() will return 2 seconds.
+And if you waited 4 seconds or more, failure() will return 0.
+
+=item * B<delay_on_success> => I<ufloat> (default: 0)
+
+Number of seconds to wait after a success.
+
+=item * B<initial_delay1>* => I<ufloat>
+
+Initial delay for the first attempt after failure, in seconds.
+
+=item * B<initial_delay2>* => I<ufloat>
+
+Initial delay for the second attempt after failure, in seconds.
+
+=item * B<jitter_factor> => I<float>
+
+How much to add randomness.
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+=item * B<max_actual_duration> => I<ufloat> (default: 0)
+
+Maximum number of seconds for all of the attempts (0 means unlimited).
+
+If set to a positive number, will limit the number of seconds for all of the
+attempts. This setting is used to limit the amount of time you are willing to
+spend on a task. For example, when using the Exponential strategy of
+initial_delay=3 and max_attempts=10, the delays will be 3, 6, 12, 24, ... If
+failures are logged according to the suggested delays, and max_actual_duration
+is set to 21 seconds, then the third failure() will return -1 instead of 24
+because 3+6+12 >= 21, even though max_attempts has not been exceeded.
+
+=item * B<max_attempts> => I<uint> (default: 0)
+
+Maximum number consecutive failures before giving up.
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+=item * B<max_delay> => I<ufloat>
+
+Maximum delay time, in seconds.
+
+=item * B<min_delay> => I<ufloat> (default: 0)
+
+Maximum delay time, in seconds.
+
+=back
+
+Return value: (obj)
+
+=head1 HOMEPAGE
+
+Please visit the project's homepage at L<https://metacpan.org/release/Algorithm-Backoff>.
+
+=head1 SOURCE
+
+Source repository is at L<https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+=head1 BUGS
+
+Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+When submitting a bug or request, please include a test-file or a
+patch to an existing test-file that illustrates the bug or desired
+feature.
+
+=head1 SEE ALSO
+
+L<https://en.wikipedia.org/wiki/Fibonacci_number>
+
+L<Algorithm::Backoff>
+
+Other C<Algorithm::Backoff::*> classes.
+
+=head1 AUTHOR
+
+perlancar <perlancar@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/lib/Algorithm/Backoff/LILD.pm b/lib/Algorithm/Backoff/LILD.pm
new file mode 100644
index 0000000..ab56824
--- /dev/null
+++ b/lib/Algorithm/Backoff/LILD.pm
@@ -0,0 +1,271 @@
+package Algorithm::Backoff::LILD;
+
+our $DATE = '2019-06-20'; # DATE
+our $VERSION = '0.009'; # VERSION
+
+use strict;
+use warnings;
+
+use parent qw(Algorithm::Backoff);
+
+our %SPEC;
+
+$SPEC{new} = {
+ v => 1.1,
+ is_class_meth => 1,
+ is_func => 0,
+ args => {
+ %Algorithm::Backoff::attr_consider_actual_delay,
+ %Algorithm::Backoff::attr_max_actual_duration,
+ %Algorithm::Backoff::attr_max_attempts,
+ %Algorithm::Backoff::attr_jitter_factor,
+ %Algorithm::Backoff::attr_max_delay,
+ %Algorithm::Backoff::attr_min_delay,
+ %Algorithm::Backoff::attr_initial_delay,
+ %Algorithm::Backoff::attr_delay_increment_on_failure,
+ %Algorithm::Backoff::attr_delay_increment_on_success,
+ },
+ result_naked => 1,
+ result => {
+ schema => 'obj*',
+ },
+};
+
+sub _success {
+ my ($self, $timestamp) = @_;
+
+ unless (defined $self->{_prev_delay}) {
+ return $self->{_prev_delay} = $self->{initial_delay};
+ }
+
+ my $delay = $self->{_prev_delay} + $self->{delay_increment_on_success};
+
+ $delay;
+}
+
+sub _failure {
+ my ($self, $timestamp) = @_;
+
+ unless (defined $self->{_prev_delay}) {
+ return $self->{_prev_delay} = $self->{initial_delay};
+ }
+
+ my $delay = $self->{_prev_delay} + $self->{delay_increment_on_failure};
+
+ $delay;
+}
+
+1;
+# ABSTRACT: Linear Increment, Linear Decrement (LILD) backoff
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Algorithm::Backoff::LILD - Linear Increment, Linear Decrement (LILD) backoff
+
+=head1 VERSION
+
+This document describes version 0.009 of Algorithm::Backoff::LILD (from Perl distribution Algorithm-Backoff), released on 2019-06-20.
+
+=head1 SYNOPSIS
+
+ use Algorithm::Backoff::LILD;
+
+ # 1. instantiate
+
+ my $ab = Algorithm::Backoff::LILD->new(
+ #consider_actual_delay => 1, # optional, default 0
+ #max_actual_duration => 0, # optional, default 0 (retry endlessly)
+ #max_attempts => 0, # optional, default 0 (retry endlessly)
+ #jitter_factor => 0.25, # optional, default 0
+ min_delay => 1, # optional, default 0
+ #max_delay => 100, # optional
+ initial_delay => 3, # required
+ delay_increment_on_failure => 4, # required
+ delay_increment_on_success => -5, # required
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional but must be monotonically increasing.
+
+ # for example, using the parameters initial_delay=3,
+ # delay_increment_on_failure=4, delay_increment_on_success=-5, min_delay=1:
+
+ my $secs;
+ $secs = $ab->failure(); # => 3 (= initial_delay)
+ $secs = $ab->failure(); # => 7 (3 + 4)
+ $secs = $ab->failure(); # => 11 (7 + 4)
+ $secs = $ab->success(); # => 6 (11 - 5)
+ $secs = $ab->success(); # => 1 (6 - 5)
+ $secs = $ab->success(); # => 1 (max(1 - 5, 0, min_delay=1))
+ $secs = $ab->failure(); # => 5 (1 + 4)
+
+Illustration using CLI L<show-backoff-delays> (3 failures followed by 4
+successes, followed by 3 failures):
+
+ % show-backoff-delays -a LILD --initial-delay 3 --min-delay 1 \
+ --delay-increment-on-failure 4 --delay-increment-on-success -5 \
+ 0 0 0 1 1 1 1 0 0 0
+ 3
+ 7
+ 11
+ 6
+ 1
+ 1
+ 1
+ 5
+ 9
+ 13
+
+=head1 DESCRIPTION
+
+Upon failure, this backoff algorithm calculates the next delay as:
+
+ D1 = initial_delay
+ D2 = max(min(D1 + delay_increment_on_failure, max_delay), min_delay)
+ ...
+
+Upon success, the next delay is calculated as:
+
+ D1 = initial_delay
+ D2 = max(D1 + delay_increment_on_success, min_delay, initial_delay)
+ ...
+
+C<initial_delay>, C<delay_increment_on_failure>, and
+C<delay_increment_on_success> are required.
+
+There are limits on the number of attempts (`max_attempts`) and total duration
+(`max_actual_duration`).
+
+It is recommended to add a jitter factor, e.g. 0.25 to add some randomness to
+avoid "thundering herd problem".
+
+=head1 METHODS
+
+
+=head2 new
+
+Usage:
+
+ new(%args) -> obj
+
+This function is not exported.
+
+Arguments ('*' denotes required arguments):
+
+=over 4
+
+=item * B<consider_actual_delay> => I<bool> (default: 0)
+
+Whether to consider actual delay.
+
+If set to true, will take into account the actual delay (timestamp difference).
+For example, when using the Constant strategy of delay=2, you log failure()
+again right after the previous failure() (i.e. specify the same timestamp).
+failure() will then return ~2+2 = 4 seconds. On the other hand, if you waited 2
+seconds before calling failure() again (i.e. specify the timestamp that is 2
+seconds larger than the previous timestamp), failure() will return 2 seconds.
+And if you waited 4 seconds or more, failure() will return 0.
+
+=item * B<delay_increment_on_failure>* => I<float>
+
+How much to add to previous delay, in seconds, upon failure (e.g. 5).
+
+=item * B<delay_increment_on_success>* => I<float>
+
+How much to add to previous delay, in seconds, upon success (e.g. -5).
+
+=item * B<initial_delay>* => I<ufloat>
+
+Initial delay for the first attempt after failure, in seconds.
+
+=item * B<jitter_factor> => I<float>
+
+How much to add randomness.
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+=item * B<max_actual_duration> => I<ufloat> (default: 0)
+
+Maximum number of seconds for all of the attempts (0 means unlimited).
+
+If set to a positive number, will limit the number of seconds for all of the
+attempts. This setting is used to limit the amount of time you are willing to
+spend on a task. For example, when using the Exponential strategy of
+initial_delay=3 and max_attempts=10, the delays will be 3, 6, 12, 24, ... If
+failures are logged according to the suggested delays, and max_actual_duration
+is set to 21 seconds, then the third failure() will return -1 instead of 24
+because 3+6+12 >= 21, even though max_attempts has not been exceeded.
+
+=item * B<max_attempts> => I<uint> (default: 0)
+
+Maximum number consecutive failures before giving up.
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+=item * B<max_delay> => I<ufloat>
+
+Maximum delay time, in seconds.
+
+=item * B<min_delay> => I<ufloat> (default: 0)
+
+Maximum delay time, in seconds.
+
+=back
+
+Return value: (obj)
+
+=head1 HOMEPAGE
+
+Please visit the project's homepage at L<https://metacpan.org/release/Algorithm-Backoff>.
+
+=head1 SOURCE
+
+Source repository is at L<https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+=head1 BUGS
+
+Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+When submitting a bug or request, please include a test-file or a
+patch to an existing test-file that illustrates the bug or desired
+feature.
+
+=head1 SEE ALSO
+
+L<Algorithm::Backoff::LIMD>
+
+L<Algorithm::Backoff::MILD>
+
+L<Algorithm::Backoff::MIMD>
+
+L<Algorithm::Backoff>
+
+Other C<Algorithm::Backoff::*> classes.
+
+=head1 AUTHOR
+
+perlancar <perlancar@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/lib/Algorithm/Backoff/LIMD.pm b/lib/Algorithm/Backoff/LIMD.pm
new file mode 100644
index 0000000..e713f80
--- /dev/null
+++ b/lib/Algorithm/Backoff/LIMD.pm
@@ -0,0 +1,269 @@
+package Algorithm::Backoff::LIMD;
+
+our $DATE = '2019-06-20'; # DATE
+our $VERSION = '0.009'; # VERSION
+
+use strict;
+use warnings;
+
+use parent qw(Algorithm::Backoff);
+
+our %SPEC;
+
+$SPEC{new} = {
+ v => 1.1,
+ is_class_meth => 1,
+ is_func => 0,
+ args => {
+ %Algorithm::Backoff::attr_consider_actual_delay,
+ %Algorithm::Backoff::attr_max_actual_duration,
+ %Algorithm::Backoff::attr_max_attempts,
+ %Algorithm::Backoff::attr_jitter_factor,
+ %Algorithm::Backoff::attr_max_delay,
+ %Algorithm::Backoff::attr_min_delay,
+ %Algorithm::Backoff::attr_initial_delay,
+ %Algorithm::Backoff::attr_delay_increment_on_failure,
+ %Algorithm::Backoff::attr_delay_multiple_on_success,
+ },
+ result_naked => 1,
+ result => {
+ schema => 'obj*',
+ },
+};
+
+sub _success {
+ my ($self, $timestamp) = @_;
+
+ unless (defined $self->{_prev_delay}) {
+ return $self->{_prev_delay} = $self->{initial_delay};
+ }
+
+ my $delay = $self->{_prev_delay} * $self->{delay_multiple_on_success};
+
+ $delay;
+}
+
+sub _failure {
+ my ($self, $timestamp) = @_;
+
+ unless (defined $self->{_prev_delay}) {
+ return $self->{_prev_delay} = $self->{initial_delay};
+ }
+
+ my $delay = $self->{_prev_delay} + $self->{delay_increment_on_failure};
+
+ $delay;
+}
+
+1;
+# ABSTRACT: Linear Increment, Multiplicative Decrement (LIMD) backoff
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Algorithm::Backoff::LIMD - Linear Increment, Multiplicative Decrement (LIMD) backoff
+
+=head1 VERSION
+
+This document describes version 0.009 of Algorithm::Backoff::LIMD (from Perl distribution Algorithm-Backoff), released on 2019-06-20.
+
+=head1 SYNOPSIS
+
+ use Algorithm::Backoff::LIMD;
+
+ # 1. instantiate
+
+ my $ab = Algorithm::Backoff::LIMD->new(
+ #consider_actual_delay => 1, # optional, default 0
+ #max_actual_duration => 0, # optional, default 0 (retry endlessly)
+ #max_attempts => 0, # optional, default 0 (retry endlessly)
+ #jitter_factor => 0.25, # optional, default 0
+ min_delay => 1, # optional, default 0
+ #max_delay => 100, # optional
+ initial_delay => 2, # required
+ delay_increment_on_failure => 4, # required
+ delay_multiple_on_success => 0.2, # required
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional but must be monotonically increasing.
+
+ # for example, using the parameters initial_delay=2,
+ # delay_increment_on_failure=4, delay_multiple_on_success=0.2, min_delay=1:
+
+ my $secs;
+ $secs = $ab->failure(); # => 2 (= initial_delay)
+ $secs = $ab->failure(); # => 6 (2 + 4)
+ $secs = $ab->failure(); # => 10 (2 + 4)
+ $secs = $ab->success(); # => 2 (10 * 0.2)
+ $secs = $ab->success(); # => 1 (max(2 * 0.2, 1))
+ $secs = $ab->failure(); # => 5 (1 + 4)
+
+Illustration using CLI L<show-backoff-delays> (3 failures followed by 3
+successes, followed by 3 failures):
+
+ % show-backoff-delays -a LILD --initial-delay 2 --min-delay 1 \
+ --delay-increment-on-failure 4 --delay-multiple-on-success 0.2 \
+ 0 0 0 1 1 1 0 0 0
+ 2
+ 6
+ 10
+ 2
+ 1
+ 1
+ 5
+ 9
+ 13
+
+=head1 DESCRIPTION
+
+Upon failure, this backoff algorithm calculates the next delay as:
+
+ D1 = initial_delay
+ D2 = min(D1 + delay_increment_on_failure, max_delay)
+ ...
+
+Upon success, the next delay is calculated as:
+
+ D1 = initial_delay
+ D2 = max(D1 * delay_multiple_on_success, min_delay)
+ ...
+
+C<initial_delay>, C<delay_increment_on_failure>, and
+C<delay_multiple_on_success> are required.
+
+There are limits on the number of attempts (`max_attempts`) and total duration
+(`max_actual_duration`).
+
+It is recommended to add a jitter factor, e.g. 0.25 to add some randomness to
+avoid "thundering herd problem".
+
+=head1 METHODS
+
+
+=head2 new
+
+Usage:
+
+ new(%args) -> obj
+
+This function is not exported.
+
+Arguments ('*' denotes required arguments):
+
+=over 4
+
+=item * B<consider_actual_delay> => I<bool> (default: 0)
+
+Whether to consider actual delay.
+
+If set to true, will take into account the actual delay (timestamp difference).
+For example, when using the Constant strategy of delay=2, you log failure()
+again right after the previous failure() (i.e. specify the same timestamp).
+failure() will then return ~2+2 = 4 seconds. On the other hand, if you waited 2
+seconds before calling failure() again (i.e. specify the timestamp that is 2
+seconds larger than the previous timestamp), failure() will return 2 seconds.
+And if you waited 4 seconds or more, failure() will return 0.
+
+=item * B<delay_increment_on_failure>* => I<float>
+
+How much to add to previous delay, in seconds, upon failure (e.g. 5).
+
+=item * B<delay_multiple_on_success>* => I<ufloat>
+
+How much to multiple previous delay, upon success (e.g. 0.5).
+
+=item * B<initial_delay>* => I<ufloat>
+
+Initial delay for the first attempt after failure, in seconds.
+
+=item * B<jitter_factor> => I<float>
+
+How much to add randomness.
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+=item * B<max_actual_duration> => I<ufloat> (default: 0)
+
+Maximum number of seconds for all of the attempts (0 means unlimited).
+
+If set to a positive number, will limit the number of seconds for all of the
+attempts. This setting is used to limit the amount of time you are willing to
+spend on a task. For example, when using the Exponential strategy of
+initial_delay=3 and max_attempts=10, the delays will be 3, 6, 12, 24, ... If
+failures are logged according to the suggested delays, and max_actual_duration
+is set to 21 seconds, then the third failure() will return -1 instead of 24
+because 3+6+12 >= 21, even though max_attempts has not been exceeded.
+
+=item * B<max_attempts> => I<uint> (default: 0)
+
+Maximum number consecutive failures before giving up.
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+=item * B<max_delay> => I<ufloat>
+
+Maximum delay time, in seconds.
+
+=item * B<min_delay> => I<ufloat> (default: 0)
+
+Maximum delay time, in seconds.
+
+=back
+
+Return value: (obj)
+
+=head1 HOMEPAGE
+
+Please visit the project's homepage at L<https://metacpan.org/release/Algorithm-Backoff>.
+
+=head1 SOURCE
+
+Source repository is at L<https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+=head1 BUGS
+
+Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+When submitting a bug or request, please include a test-file or a
+patch to an existing test-file that illustrates the bug or desired
+feature.
+
+=head1 SEE ALSO
+
+L<Algorithm::Backoff::LILD>
+
+L<Algorithm::Backoff::MILD>
+
+L<Algorithm::Backoff::MIMD>
+
+L<Algorithm::Backoff>
+
+Other C<Algorithm::Backoff::*> classes.
+
+=head1 AUTHOR
+
+perlancar <perlancar@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/lib/Algorithm/Backoff/MILD.pm b/lib/Algorithm/Backoff/MILD.pm
new file mode 100644
index 0000000..b703717
--- /dev/null
+++ b/lib/Algorithm/Backoff/MILD.pm
@@ -0,0 +1,272 @@
+package Algorithm::Backoff::MILD;
+
+our $DATE = '2019-06-20'; # DATE
+our $VERSION = '0.009'; # VERSION
+
+use strict;
+use warnings;
+
+use parent qw(Algorithm::Backoff);
+
+our %SPEC;
+
+$SPEC{new} = {
+ v => 1.1,
+ is_class_meth => 1,
+ is_func => 0,
+ args => {
+ %Algorithm::Backoff::attr_consider_actual_delay,
+ %Algorithm::Backoff::attr_max_actual_duration,
+ %Algorithm::Backoff::attr_max_attempts,
+ %Algorithm::Backoff::attr_jitter_factor,
+ %Algorithm::Backoff::attr_max_delay,
+ %Algorithm::Backoff::attr_min_delay,
+ %Algorithm::Backoff::attr_initial_delay,
+ %Algorithm::Backoff::attr_delay_multiple_on_failure,
+ %Algorithm::Backoff::attr_delay_increment_on_success,
+ },
+ result_naked => 1,
+ result => {
+ schema => 'obj*',
+ },
+};
+
+sub _success {
+ my ($self, $timestamp) = @_;
+
+ unless (defined $self->{_prev_delay}) {
+ return $self->{_prev_delay} = $self->{initial_delay};
+ }
+
+ my $delay = $self->{_prev_delay} + $self->{delay_increment_on_success};
+
+ $delay;
+}
+
+sub _failure {
+ my ($self, $timestamp) = @_;
+
+ unless (defined $self->{_prev_delay}) {
+ return $self->{_prev_delay} = $self->{initial_delay};
+ }
+
+ my $delay = $self->{_prev_delay} * $self->{delay_multiple_on_failure};
+
+ $delay;
+}
+
+1;
+# ABSTRACT: Multiplicative Increment, Linear Decrement (MILD) backoff
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Algorithm::Backoff::MILD - Multiplicative Increment, Linear Decrement (MILD) backoff
+
+=head1 VERSION
+
+This document describes version 0.009 of Algorithm::Backoff::MILD (from Perl distribution Algorithm-Backoff), released on 2019-06-20.
+
+=head1 SYNOPSIS
+
+ use Algorithm::Backoff::MILD;
+
+ # 1. instantiate
+
+ my $ab = Algorithm::Backoff::MILD->new(
+ #consider_actual_delay => 1, # optional, default 0
+ #max_actual_duration => 0, # optional, default 0 (retry endlessly)
+ #max_attempts => 0, # optional, default 0 (retry endlessly)
+ #jitter_factor => 0.25, # optional, default 0
+ #min_delay => 2, # optional, default 0
+ #max_delay => 100, # optional
+ initial_delay => 1, # required
+ delay_multiple_on_failure => 1.5, # required
+ delay_increment_on_success => -2, # required
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional but must be monotonically increasing.
+
+ # for example, using the parameters initial_delay=1,
+ # delay_multiple_on_failure=1.5, delay_increment_on_success=-2, min_delay=0.5:
+
+ my $secs;
+ $secs = $ab->failure(); # => 1 (= initial_delay)
+ $secs = $ab->failure(); # => 1.5 (1 * 1.5)
+ $secs = $ab->failure(); # => 2.25 (1.5 * 1.5)
+ $secs = $ab->success(); # => 0.25 (2.25 - 2)
+ $secs = $ab->success(); # => 1 (max(0.25 - 2, 0.5, 1))
+ $secs = $ab->failure(); # => 1.5 (1 * 1.5)
+
+Illustration using CLI L<show-backoff-delays> (4 failures followed by 5
+successes, followed by 3 failures):
+
+ % show-backoff-delays -a MILD --initial-delay 3 --min-delay 1 \
+ --delay-multiple-on-failure 2 --delay-increment-on-success -5 \
+ 0 0 0 0 1 1 1 1 1 0 0 0
+ 3
+ 6
+ 12
+ 24
+ 19
+ 14
+ 9
+ 4
+ 1
+ 2
+ 4
+
+=head1 DESCRIPTION
+
+Upon failure, this backoff algorithm calculates the next delay as:
+
+ D1 = initial_delay
+ D2 = max(min(D1 * delay_multiple_on_failure, max_delay), min_delay)
+ ...
+
+Upon success, the next delay is calculated as:
+
+ D1 = initial_delay
+ D2 = max(D1 + delay_increment_on_success, min_delay, initial_delay)
+ ...
+
+C<initial_delay>, C<delay_multiple_on_failure>, and
+C<delay_increment_on_success> are required. C<initial_delay> and C<min_delay>
+should be larger than zero; otherwise the next delays will all be zero.
+
+There are limits on the number of attempts (`max_attempts`) and total duration
+(`max_actual_duration`).
+
+It is recommended to add a jitter factor, e.g. 0.25 to add some randomness to
+avoid "thundering herd problem".
+
+=head1 METHODS
+
+
+=head2 new
+
+Usage:
+
+ new(%args) -> obj
+
+This function is not exported.
+
+Arguments ('*' denotes required arguments):
+
+=over 4
+
+=item * B<consider_actual_delay> => I<bool> (default: 0)
+
+Whether to consider actual delay.
+
+If set to true, will take into account the actual delay (timestamp difference).
+For example, when using the Constant strategy of delay=2, you log failure()
+again right after the previous failure() (i.e. specify the same timestamp).
+failure() will then return ~2+2 = 4 seconds. On the other hand, if you waited 2
+seconds before calling failure() again (i.e. specify the timestamp that is 2
+seconds larger than the previous timestamp), failure() will return 2 seconds.
+And if you waited 4 seconds or more, failure() will return 0.
+
+=item * B<delay_increment_on_success>* => I<float>
+
+How much to add to previous delay, in seconds, upon success (e.g. -5).
+
+=item * B<delay_multiple_on_failure>* => I<ufloat>
+
+How much to multiple previous delay, upon failure (e.g. 1.5).
+
+=item * B<initial_delay>* => I<ufloat>
+
+Initial delay for the first attempt after failure, in seconds.
+
+=item * B<jitter_factor> => I<float>
+
+How much to add randomness.
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+=item * B<max_actual_duration> => I<ufloat> (default: 0)
+
+Maximum number of seconds for all of the attempts (0 means unlimited).
+
+If set to a positive number, will limit the number of seconds for all of the
+attempts. This setting is used to limit the amount of time you are willing to
+spend on a task. For example, when using the Exponential strategy of
+initial_delay=3 and max_attempts=10, the delays will be 3, 6, 12, 24, ... If
+failures are logged according to the suggested delays, and max_actual_duration
+is set to 21 seconds, then the third failure() will return -1 instead of 24
+because 3+6+12 >= 21, even though max_attempts has not been exceeded.
+
+=item * B<max_attempts> => I<uint> (default: 0)
+
+Maximum number consecutive failures before giving up.
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+=item * B<max_delay> => I<ufloat>
+
+Maximum delay time, in seconds.
+
+=item * B<min_delay> => I<ufloat> (default: 0)
+
+Maximum delay time, in seconds.
+
+=back
+
+Return value: (obj)
+
+=head1 HOMEPAGE
+
+Please visit the project's homepage at L<https://metacpan.org/release/Algorithm-Backoff>.
+
+=head1 SOURCE
+
+Source repository is at L<https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+=head1 BUGS
+
+Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+When submitting a bug or request, please include a test-file or a
+patch to an existing test-file that illustrates the bug or desired
+feature.
+
+=head1 SEE ALSO
+
+L<Algorithm::Backoff::LILD>
+
+L<Algorithm::Backoff::LIMD>
+
+L<Algorithm::Backoff::MIMD>
+
+L<Algorithm::Backoff>
+
+Other C<Algorithm::Backoff::*> classes.
+
+=head1 AUTHOR
+
+perlancar <perlancar@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/lib/Algorithm/Backoff/MIMD.pm b/lib/Algorithm/Backoff/MIMD.pm
new file mode 100644
index 0000000..cb13bab
--- /dev/null
+++ b/lib/Algorithm/Backoff/MIMD.pm
@@ -0,0 +1,274 @@
+package Algorithm::Backoff::MIMD;
+
+our $DATE = '2019-06-20'; # DATE
+our $VERSION = '0.009'; # VERSION
+
+use strict;
+use warnings;
+
+use parent qw(Algorithm::Backoff);
+
+our %SPEC;
+
+$SPEC{new} = {
+ v => 1.1,
+ is_class_meth => 1,
+ is_func => 0,
+ args => {
+ %Algorithm::Backoff::attr_consider_actual_delay,
+ %Algorithm::Backoff::attr_max_actual_duration,
+ %Algorithm::Backoff::attr_max_attempts,
+ %Algorithm::Backoff::attr_jitter_factor,
+ %Algorithm::Backoff::attr_max_delay,
+ %Algorithm::Backoff::attr_min_delay,
+ %Algorithm::Backoff::attr_initial_delay,
+ %Algorithm::Backoff::attr_delay_multiple_on_failure,
+ %Algorithm::Backoff::attr_delay_multiple_on_success,
+ },
+ result_naked => 1,
+ result => {
+ schema => 'obj*',
+ },
+};
+
+sub _success {
+ my ($self, $timestamp) = @_;
+
+ unless (defined $self->{_prev_delay}) {
+ return $self->{_prev_delay} = $self->{initial_delay};
+ }
+
+ my $delay = $self->{_prev_delay} * $self->{delay_multiple_on_success};
+
+ $delay;
+}
+
+sub _failure {
+ my ($self, $timestamp) = @_;
+
+ unless (defined $self->{_prev_delay}) {
+ return $self->{_prev_delay} = $self->{initial_delay};
+ }
+
+ my $delay = $self->{_prev_delay} * $self->{delay_multiple_on_failure};
+
+ $delay;
+}
+
+1;
+# ABSTRACT: Multiplicative Increment, Multiplicative Decrement (MIMD) backoff
+
+__END__
+
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+Algorithm::Backoff::MIMD - Multiplicative Increment, Multiplicative Decrement (MIMD) backoff
+
+=head1 VERSION
+
+This document describes version 0.009 of Algorithm::Backoff::MIMD (from Perl distribution Algorithm-Backoff), released on 2019-06-20.
+
+=head1 SYNOPSIS
+
+ use Algorithm::Backoff::MIMD;
+
+ # 1. instantiate
+
+ my $ab = Algorithm::Backoff::MIMD->new(
+ #consider_actual_delay => 1, # optional, default 0
+ #max_actual_duration => 0, # optional, default 0 (retry endlessly)
+ #max_attempts => 0, # optional, default 0 (retry endlessly)
+ #jitter_factor => 0.25, # optional, default 0
+ min_delay => 2, # optional, default 0
+ #max_delay => 100, # optional
+ initial_delay => 3, # required
+ delay_multiple_on_failure => 2, # required
+ delay_multiple_on_success => 0.5, # required
+ );
+
+ # 2. log success/failure and get a new number of seconds to delay, timestamp is
+ # optional but must be monotonically increasing.
+
+ # for example, using the parameters initial_delay=3,
+ # delay_multiple_on_failure=2, delay_multiple_on_success=0.5, min_delay=2:
+
+ my $secs;
+ $secs = $ab->failure(); # => 3 (= initial_delay)
+ $secs = $ab->failure(); # => 6 (3 * 2)
+ $secs = $ab->failure(); # => 12 (6 * 2)
+ $secs = $ab->success(); # => 6 (12 * 0.5)
+ $secs = $ab->success(); # => 3 (6 * 0.5)
+ $secs = $ab->success(); # => 2 (max(3*0.5, min_delay=2))
+ $secs = $ab->failure(); # => 4 (2 * 2)
+
+Illustration using CLI L<show-backoff-delays> (4 failures followed by 5
+successes, followed by 3 failures):
+
+ % show-backoff-delays -a MIMD --initial-delay 3 --min-delay 2 \
+ --delay-multiple-on-failure 2 --delay-multiple-on-success 0.5 \
+ 0 0 0 0 1 1 1 1 1 0 0 0
+ 3
+ 6
+ 12
+ 24
+ 12
+ 6
+ 3
+ 2
+ 2
+ 4
+ 8
+ 16
+
+=head1 DESCRIPTION
+
+Upon failure, this backoff algorithm calculates the next delay as:
+
+ D1 = initial_delay
+ D2 = max(min(D1 * delay_multiple_on_failure, max_delay), min_delay)
+ ...
+
+Upon success, the next delay is calculated as:
+
+ D1 = initial_delay
+ D2 = max(min(D1 * delay_multiple_on_success, max_delay), min_delay)
+ ...
+
+C<initial_delay>, C<delay_multiple_on_failure>, and C<delay_multiple_on_success>
+are required. C<initial_delay> and C<min_delay> should be larger than zero;
+otherwise the next delays will all be zero.
+
+There are limits on the number of attempts (`max_attempts`) and total duration
+(`max_actual_duration`).
+
+It is recommended to add a jitter factor, e.g. 0.25 to add some randomness to
+avoid "thundering herd problem".
+
+=head1 METHODS
+
+
+=head2 new
+
+Usage:
+
+ new(%args) -> obj
+
+This function is not exported.
+
+Arguments ('*' denotes required arguments):
+
+=over 4
+
+=item * B<consider_actual_delay> => I<bool> (default: 0)
+
+Whether to consider actual delay.
+
+If set to true, will take into account the actual delay (timestamp difference).
+For example, when using the Constant strategy of delay=2, you log failure()
+again right after the previous failure() (i.e. specify the same timestamp).
+failure() will then return ~2+2 = 4 seconds. On the other hand, if you waited 2
+seconds before calling failure() again (i.e. specify the timestamp that is 2
+seconds larger than the previous timestamp), failure() will return 2 seconds.
+And if you waited 4 seconds or more, failure() will return 0.
+
+=item * B<delay_multiple_on_failure>* => I<ufloat>
+
+How much to multiple previous delay, upon failure (e.g. 1.5).
+
+=item * B<delay_multiple_on_success>* => I<ufloat>
+
+How much to multiple previous delay, upon success (e.g. 0.5).
+
+=item * B<initial_delay>* => I<ufloat>
+
+Initial delay for the first attempt after failure, in seconds.
+
+=item * B<jitter_factor> => I<float>
+
+How much to add randomness.
+
+If you set this to a value larger than 0, the actual delay will be between a
+random number between original_delay * (1-jitter_factor) and original_delay *
+(1+jitter_factor). Jitters are usually added to avoid so-called "thundering
+herd" problem.
+
+The jitter will be applied to delay on failure as well as on success.
+
+=item * B<max_actual_duration> => I<ufloat> (default: 0)
+
+Maximum number of seconds for all of the attempts (0 means unlimited).
+
+If set to a positive number, will limit the number of seconds for all of the
+attempts. This setting is used to limit the amount of time you are willing to
+spend on a task. For example, when using the Exponential strategy of
+initial_delay=3 and max_attempts=10, the delays will be 3, 6, 12, 24, ... If
+failures are logged according to the suggested delays, and max_actual_duration
+is set to 21 seconds, then the third failure() will return -1 instead of 24
+because 3+6+12 >= 21, even though max_attempts has not been exceeded.
+
+=item * B<max_attempts> => I<uint> (default: 0)
+
+Maximum number consecutive failures before giving up.
+
+0 means to retry endlessly without ever giving up. 1 means to give up after a
+single failure (i.e. no retry attempts). 2 means to retry once after a failure.
+Note that after a success, the number of attempts is reset (as expected). So if
+max_attempts is 3, and if you fail twice then succeed, then on the next failure
+the algorithm will retry again for a maximum of 3 times.
+
+=item * B<max_delay> => I<ufloat>
+
+Maximum delay time, in seconds.
+
+=item * B<min_delay> => I<ufloat> (default: 0)
+
+Maximum delay time, in seconds.
+
+=back
+
+Return value: (obj)
+
+=head1 HOMEPAGE
+
+Please visit the project's homepage at L<https://metacpan.org/release/Algorithm-Backoff>.
+
+=head1 SOURCE
+
+Source repository is at L<https://github.com/perlancar/perl-Algorithm-Backoff>.
+
+=head1 BUGS
+
+Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Algorithm-Backoff>
+
+When submitting a bug or request, please include a test-file or a
+patch to an existing test-file that illustrates the bug or desired
+feature.
+
+=head1 SEE ALSO
+
+L<Algorithm::Backoff::LILD>
+
+L<Algorithm::Backoff::LIMD>
+
+L<Algorithm::Backoff::MILD>
+
+L<Algorithm::Backoff>
+
+Other C<Algorithm::Backoff::*> classes.
+
+=head1 AUTHOR
+
+perlancar <perlancar@cpan.org>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2019 by perlancar@cpan.org.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/t/00-compile.t b/t/00-compile.t
new file mode 100644
index 0000000..bb1187a
--- /dev/null
+++ b/t/00-compile.t
@@ -0,0 +1,67 @@
+use 5.006;
+use strict;
+use warnings;
+
+# this test was generated with Dist::Zilla::Plugin::Test::Compile 2.058
+
+use Test::More;
+
+plan tests => 8 + ($ENV{AUTHOR_TESTING} ? 1 : 0);
+
+my @module_files = (
+ 'Algorithm/Backoff.pm',
+ 'Algorithm/Backoff/Constant.pm',
+ 'Algorithm/Backoff/Exponential.pm',
+ 'Algorithm/Backoff/Fibonacci.pm',
+ 'Algorithm/Backoff/LILD.pm',
+ 'Algorithm/Backoff/LIMD.pm',
+ 'Algorithm/Backoff/MILD.pm',
+ 'Algorithm/Backoff/MIMD.pm'
+);
+
+
+
+# no fake home requested
+
+my @switches = (
+ -d 'blib' ? '-Mblib' : '-Ilib',
+);
+
+use File::Spec;
+use IPC::Open3;
+use IO::Handle;
+
+open my $stdin, '<', File::Spec->devnull or die "can't open devnull: $!";
+
+my @warnings;
+for my $lib (@module_files)
+{
+ # see L<perlfaq8/How can I capture STDERR from an external command?>
+ my $stderr = IO::Handle->new;
+
+ diag('Running: ', join(', ', map { my $str = $_; $str =~ s/'/\\'/g; q{'} . $str . q{'} }
+ $^X, @switches, '-e', "require q[$lib]"))
+ if $ENV{PERL_COMPILE_TEST_DEBUG};
+
+ my $pid = open3($stdin, '>&STDERR', $stderr, $^X, @switches, '-e', "require q[$lib]");
+ binmode $stderr, ':crlf' if $^O eq 'MSWin32';
+ my @_warnings = <$stderr>;
+ waitpid($pid, 0);
+ is($?, 0, "$lib loaded ok");
+
+ shift @_warnings if @_warnings and $_warnings[0] =~ /^Using .*\bblib/
+ and not eval { +require blib; blib->VERSION('1.01') };
+
+ if (@_warnings)
+ {
+ warn @_warnings;
+ push @warnings, @_warnings;
+ }
+}
+
+
+
+is(scalar(@warnings), 0, 'no warnings found')
+ or diag 'got warnings: ', ( Test::More->can('explain') ? Test::More::explain(\@warnings) : join("\n", '', @warnings) ) if $ENV{AUTHOR_TESTING};
+
+
diff --git a/t/01-base.t b/t/01-base.t
new file mode 100644
index 0000000..01c26f3
--- /dev/null
+++ b/t/01-base.t
@@ -0,0 +1,141 @@
+#!perl
+
+use strict;
+use warnings;
+use Test::Exception;
+use Test::More 0.98;
+
+use Algorithm::Backoff::Constant;
+
+# XXX test max_attempts for each strategy
+subtest "attr: max_attempts" => sub {
+ my $ar;
+
+ $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ max_attempts => 0,
+ );
+ isnt($ar->failure(1), -1);
+ isnt($ar->failure(2), -1);
+ isnt($ar->failure(3), -1);
+
+ $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ max_attempts => 1,
+ );
+ is($ar->failure(1), -1);
+
+ $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ max_attempts => 2,
+ );
+ isnt($ar->failure(1), -1);
+ is ($ar->failure(1), -1);
+ $ar->success(1);
+ isnt($ar->failure(1), -1);
+ is ($ar->failure(1), -1);
+};
+
+# XXX test max_actual_duration for each strategy
+subtest "attr: max_actual_duration" => sub {
+ my $ar;
+
+ $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ max_actual_duration => 4,
+ max_attempts => 3,
+ _start_timestamp => 0,
+ );
+ isnt($ar->failure(0), -1);
+ isnt($ar->failure(2), -1);
+ is ($ar->failure(4), -1);
+};
+
+# XXX test consider_actual_delay for each strategy
+subtest "attr: consider_actual_delay" => sub {
+ my $ar;
+
+ $ar = Algorithm::Backoff::Constant->new(
+ consider_actual_delay => 1,
+ delay => 2,
+ max_attempts => 0,
+ );
+
+ is($ar->failure(1), 2);
+
+ # we didn't wait, so the delay is now 2+2 = 4
+ is($ar->failure(1), 4);
+
+ # we now waited for 5 seconds, so delay is now 2-1 = 1
+ is($ar->failure(6), 1);
+
+ # we now waited for 2 seconds, so delay is now 2-1 = 1
+ is($ar->failure(8), 1);
+
+ # we now waited for 3 seconds, so delay is now 2-2 = 0
+ is($ar->failure(11), 0);
+};
+
+# XXX test jitter_factor for each strategy
+subtest "attr: jitter_factor" => sub {
+ my $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ delay_on_success => 3,
+ jitter_factor => 0.1,
+ );
+
+ rand_between_ok(sub { $ar->failure(1) }, 2*(1-0.1), 2*(1+0.1));
+ rand_between_ok(sub { $ar->success(1) }, 3*(1-0.1), 3*(1+0.1));
+
+ # jittered delay still doesn't violate min_delay and max_delay
+ $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ delay_on_success => 2,
+ min_delay => 1.8,
+ max_delay => 2.2,
+ jitter_factor => 0.5,
+ );
+ rand_between_ok(sub { $ar->failure(1) }, 1.8, 2.2);
+
+ $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ delay_on_success => 3,
+ min_delay => 2.8,
+ max_delay => 3.2,
+ jitter_factor => 0.5,
+ );
+
+ rand_between_ok(sub { $ar->success(1) }, 2.8, 3.2);
+};
+
+subtest "timestamp must not decrease" => sub {
+ my $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ );
+
+ $ar->success(2);
+ dies_ok { $ar->success(1) };
+};
+
+DONE_TESTING:
+done_testing;
+
+# XXX temporary function
+sub rand_between_ok(&$$) {
+ my ($block, $min, $max, $name) = @_;
+ my @res;
+ my %res;
+ for (1..30) {
+ my $res = $block->();
+ do {
+ ok(0, "Result #$_ is not between $min and $max ($res)");
+ return;
+ } if $res < $min || $res > $max;
+ push @res, $res;
+ $res{ $res+0 }++;
+ }
+ note "Results: ", explain(\@res);
+ keys(%res) > 1 or
+ ok(0, "Results do not seem to be random, but constant $res[0]");
+ ok(1, "Results are random between $min and $max");
+}
diff --git a/t/author-critic.t b/t/author-critic.t
new file mode 100644
index 0000000..22becf1
--- /dev/null
+++ b/t/author-critic.t
@@ -0,0 +1,15 @@
+#!perl
+
+BEGIN {
+ unless ($ENV{AUTHOR_TESTING}) {
+ print qq{1..0 # SKIP these tests are for testing by the author\n};
+ exit
+ }
+}
+
+
+use strict;
+use warnings;
+
+use Test::Perl::Critic (-profile => "perlcritic.rc") x!! -e "perlcritic.rc";
+all_critic_ok();
diff --git a/t/author-pod-coverage.t b/t/author-pod-coverage.t
new file mode 100644
index 0000000..243340f
--- /dev/null
+++ b/t/author-pod-coverage.t
@@ -0,0 +1,15 @@
+#!perl
+
+BEGIN {
+ unless ($ENV{AUTHOR_TESTING}) {
+ print qq{1..0 # SKIP these tests are for testing by the author\n};
+ exit
+ }
+}
+
+# This file was automatically generated by Dist::Zilla::Plugin::PodCoverageTests.
+
+use Test::Pod::Coverage 1.08;
+use Pod::Coverage::TrustPod;
+
+all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' });
diff --git a/t/author-pod-syntax.t b/t/author-pod-syntax.t
new file mode 100644
index 0000000..2233af0
--- /dev/null
+++ b/t/author-pod-syntax.t
@@ -0,0 +1,15 @@
+#!perl
+
+BEGIN {
+ unless ($ENV{AUTHOR_TESTING}) {
+ print qq{1..0 # SKIP these tests are for testing by the author\n};
+ exit
+ }
+}
+
+# This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests.
+use strict; use warnings;
+use Test::More;
+use Test::Pod 1.41;
+
+all_pod_files_ok();
diff --git a/t/constant.t b/t/constant.t
new file mode 100644
index 0000000..d1c1ed4
--- /dev/null
+++ b/t/constant.t
@@ -0,0 +1,38 @@
+#!perl
+
+use strict;
+use warnings;
+use Test::More 0.98;
+use Test::Number::Delta within => 1e-3;
+
+use Algorithm::Backoff::Constant;
+
+#subtest "required arguments" => sub {
+#};
+
+subtest "basics" => sub {
+ my $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ delay_on_success => 1,
+ );
+
+ is($ar->success(1), 1);
+ is($ar->success(1), 1);
+ is($ar->failure(1), 2);
+ is($ar->failure(1), 2);
+ is($ar->failure(2), 2); # test consider_actual_delay = 0
+ is($ar->failure(4), 2); # test consider_actual_delay = 0
+
+ # test using real timestamps
+ $ar = Algorithm::Backoff::Constant->new(
+ delay => 2,
+ delay_on_success => 1,
+ );
+ delta_ok($ar->success, 1);
+ delta_ok($ar->failure, 2);
+};
+
+# XXX test jitters
+
+DONE_TESTING:
+done_testing;
diff --git a/t/exponential.t b/t/exponential.t
new file mode 100644
index 0000000..927a3ab
--- /dev/null
+++ b/t/exponential.t
@@ -0,0 +1,43 @@
+#!perl
+
+use strict;
+use warnings;
+use Test::More 0.98;
+
+use Algorithm::Backoff::Exponential;
+
+#subtest "required arguments" => sub {
+#};
+
+# XXX test attr: max_attempts
+
+subtest "attr: initial_delay, max_delay, delay_on_success" => sub {
+ my $ar = Algorithm::Backoff::Exponential->new(
+ delay_on_success => 1,
+ initial_delay => 5,
+ max_delay => 100,
+ );
+
+ is($ar->failure(1), 5);
+ is($ar->failure(1), 10);
+ is($ar->failure(1), 20);
+ is($ar->failure(8), 40); # test consider_actual_delay=0
+ is($ar->failure(8), 80);
+ is($ar->failure(8), 100);
+ is($ar->success(8), 1);
+};
+
+subtest "attr: exponent_base" => sub {
+ my $ar = Algorithm::Backoff::Exponential->new(
+ initial_delay => 5,
+ exponent_base => 3,
+ );
+ is($ar->failure(1), 5);
+ is($ar->failure(1), 15);
+ is($ar->failure(1), 45);
+};
+
+# XXX test attr: jitter_factor
+
+DONE_TESTING:
+done_testing;
diff --git a/t/fibonacci.t b/t/fibonacci.t
new file mode 100644
index 0000000..bfc391c
--- /dev/null
+++ b/t/fibonacci.t
@@ -0,0 +1,34 @@
+#!perl
+
+use strict;
+use warnings;
+use Test::More 0.98;
+
+use Algorithm::Backoff::Fibonacci;
+
+#subtest "required arguments" => sub {
+#};
+
+# XXX test attr: max_attempts
+
+subtest "attr: initial_delay1, initial_delay2, max_delay, delay_on_success" => sub {
+ my $ar = Algorithm::Backoff::Fibonacci->new(
+ delay_on_success => 1,
+ initial_delay1 => 2,
+ initial_delay2 => 3,
+ max_delay => 20,
+ );
+
+ is($ar->failure(1), 2);
+ is($ar->failure(1), 3);
+ is($ar->failure(1), 5);
+ is($ar->failure(8), 8); # test consider_actual_delay=0
+ is($ar->failure(8), 13);
+ is($ar->failure(8), 20);
+ is($ar->success(8), 1);
+};
+
+# XXX test attr: jitter_factor
+
+DONE_TESTING:
+done_testing;
diff --git a/t/lild.t b/t/lild.t
new file mode 100644
index 0000000..9cd2261
--- /dev/null
+++ b/t/lild.t
@@ -0,0 +1,36 @@
+#!perl
+
+use strict;
+use warnings;
+use Test::More 0.98;
+
+use Algorithm::Backoff::LILD;
+
+#subtest "required arguments" => sub {
+#};
+
+# XXX test attr: max_attempts
+
+subtest "attr: initial_delay, delay_increment_on_failure, delay_increment_on_success, max_delay, min_delay" => sub {
+ my $ar = Algorithm::Backoff::LILD->new(
+ delay_increment_on_failure => 4,
+ delay_increment_on_success => -5,
+ initial_delay => 3,
+ min_delay => 1,
+ );
+
+ is($ar->failure(1), 3);
+ is($ar->failure(1), 7);
+ is($ar->failure(1), 11);
+ is($ar->success(1), 6);
+ is($ar->success(1), 1);
+ is($ar->success(1), 1);
+ is($ar->success(1), 1);
+ is($ar->failure(1), 5);
+ is($ar->failure(1), 9);
+};
+
+# XXX test attr: jitter_factor
+
+DONE_TESTING:
+done_testing;
diff --git a/t/limd.t b/t/limd.t
new file mode 100644
index 0000000..46864dc
--- /dev/null
+++ b/t/limd.t
@@ -0,0 +1,35 @@
+#!perl
+
+use strict;
+use warnings;
+use Test::More 0.98;
+
+use Algorithm::Backoff::LIMD;
+
+#subtest "required arguments" => sub {
+#};
+
+# XXX test attr: max_attempts
+
+subtest "attr: initial_delay, delay_increment_on_failure, delay_multiple_on_success, max_delay, min_delay" => sub {
+ my $ar = Algorithm::Backoff::LIMD->new(
+ delay_increment_on_failure => 4,
+ delay_multiple_on_success => 0.2,
+ initial_delay => 2,
+ min_delay => 1,
+ );
+
+ is($ar->failure(1), 2);
+ is($ar->failure(1), 6);
+ is($ar->failure(1), 10);
+ is($ar->success(1), 2);
+ is($ar->success(1), 1);
+ is($ar->success(1), 1);
+ is($ar->failure(1), 5);
+ is($ar->failure(1), 9);
+};
+
+# XXX test attr: jitter_factor
+
+DONE_TESTING:
+done_testing;
diff --git a/t/mild.t b/t/mild.t
new file mode 100644
index 0000000..72c9d9e
--- /dev/null
+++ b/t/mild.t
@@ -0,0 +1,40 @@
+#!perl
+
+use strict;
+use warnings;
+use Test::More 0.98;
+
+use Algorithm::Backoff::MILD;
+
+#subtest "required arguments" => sub {
+#};
+
+# XXX test attr: max_attempts
+
+subtest "attr: initial_delay, delay_multiple_on_failure, delay_increment_on_success, max_delay, min_delay" => sub {
+ my $ar = Algorithm::Backoff::MILD->new(
+ delay_multiple_on_failure => 2,
+ delay_increment_on_success => -2,
+ initial_delay => 3,
+ min_delay => 1,
+ );
+
+ is($ar->failure(1), 3);
+ is($ar->failure(1), 6);
+ is($ar->failure(1), 12);
+ is($ar->success(1), 10);
+ is($ar->success(1), 8);
+ is($ar->success(1), 6);
+ is($ar->success(1), 4);
+ is($ar->success(1), 2);
+ is($ar->success(1), 1);
+ is($ar->success(1), 1);
+ is($ar->success(1), 1);
+ is($ar->failure(1), 2);
+ is($ar->failure(1), 4);
+};
+
+# XXX test attr: jitter_factor
+
+DONE_TESTING:
+done_testing;
diff --git a/t/mimd.t b/t/mimd.t
new file mode 100644
index 0000000..798b640
--- /dev/null
+++ b/t/mimd.t
@@ -0,0 +1,37 @@
+#!perl
+
+use strict;
+use warnings;
+use Test::More 0.98;
+
+use Algorithm::Backoff::MIMD;
+
+#subtest "required arguments" => sub {
+#};
+
+# XXX test attr: max_attempts
+
+subtest "attr: initial_delay, delay_multiple_on_failure, delay_multiple_on_success, max_delay, min_delay" => sub {
+ my $ar = Algorithm::Backoff::MIMD->new(
+ delay_multiple_on_failure => 2,
+ delay_multiple_on_success => 0.5,
+ initial_delay => 3,
+ min_delay => 2,
+ );
+
+ is($ar->failure(1), 3);
+ is($ar->failure(1), 6);
+ is($ar->failure(1), 12);
+ is($ar->success(1), 6);
+ is($ar->success(1), 3);
+ is($ar->success(1), 2);
+ is($ar->success(1), 2);
+ is($ar->success(1), 2);
+ is($ar->failure(1), 4);
+ is($ar->failure(1), 8);
+};
+
+# XXX test attr: jitter_factor
+
+DONE_TESTING:
+done_testing;
diff --git a/t/release-rinci.t b/t/release-rinci.t
new file mode 100644
index 0000000..dc946ff
--- /dev/null
+++ b/t/release-rinci.t
@@ -0,0 +1,19 @@
+#!perl
+
+BEGIN {
+ unless ($ENV{RELEASE_TESTING}) {
+ print qq{1..0 # SKIP these tests are for release candidate testing\n};
+ exit
+ }
+}
+
+
+# This file was automatically generated by Dist::Zilla::Plugin::Test::Rinci.
+
+use Test::More;
+
+eval "use Test::Rinci 0.01";
+plan skip_all => "Test::Rinci 0.01 required for testing Rinci metadata"
+ if $@;
+
+metadata_in_all_modules_ok();
diff --git a/weaver.ini b/weaver.ini
new file mode 100644
index 0000000..6b85c6f
--- /dev/null
+++ b/weaver.ini
@@ -0,0 +1 @@
+[@Author::PERLANCAR]