back_pressure 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cdc962be27f2aaa28ea3d057516d1e1073f053e71bbe231ecffd9b952a1c6f1e
4
+ data.tar.gz: f0aba0b93e5457bbab21db5f604d9da0f73e1e9fed20dccfff83a0f00ca0ec30
5
+ SHA512:
6
+ metadata.gz: b2a940dde8f7f26ef05eebe9a0d91f0b9022b43ce0926b630643645a878ca2614f60e30e5ca475868b825f2ce9502d0b56b40ab6b72caf58ea788d4bf2343675
7
+ data.tar.gz: 7a5fd47d2f819cbcd972f69ed52679f23d2b4c3c413e12d4d9280d181eb60dfc85623e45aa34d3284c118458155a93a6ba2d80b202f5d1fcc54674c21390ae91
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /Gemfile.lock
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,13 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+
6
+ matrix:
7
+ include:
8
+ - jdk: openjdk8
9
+ rvm: jruby-9.1.17.0
10
+ - jdk: openjdk11
11
+ rvm: jruby-9.2.7.0
12
+ - rvm: 2.6
13
+ before_install: gem install bundler
@@ -0,0 +1,3 @@
1
+ # 1.0.0
2
+ - Added `Executor` interface
3
+ - Implemented `GatedExecutor`
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at conduct@yaauie.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in back_pressure.gemspec
4
+ gemspec
@@ -0,0 +1,194 @@
1
+ Apache License
2
+ ==============
3
+
4
+ _Version 2.0, January 2004_
5
+ _&lt;<http://www.apache.org/licenses/>&gt;_
6
+
7
+ ### Terms and Conditions for use, reproduction, and distribution
8
+
9
+ #### 1. Definitions
10
+
11
+ “License” shall mean the terms and conditions for use, reproduction, and
12
+ distribution as defined by Sections 1 through 9 of this document.
13
+
14
+ “Licensor” shall mean the copyright owner or entity authorized by the copyright
15
+ owner that is granting the License.
16
+
17
+ “Legal Entity” shall mean the union of the acting entity and all other entities
18
+ that control, are controlled by, or are under common control with that entity.
19
+ For the purposes of this definition, “control” means **(i)** the power, direct or
20
+ indirect, to cause the direction or management of such entity, whether by
21
+ contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or **(iii)** beneficial ownership of such entity.
23
+
24
+ “You” (or “Your”) shall mean an individual or Legal Entity exercising
25
+ permissions granted by this License.
26
+
27
+ “Source” form shall mean the preferred form for making modifications, including
28
+ but not limited to software source code, documentation source, and configuration
29
+ files.
30
+
31
+ “Object” form shall mean any form resulting from mechanical transformation or
32
+ translation of a Source form, including but not limited to compiled object code,
33
+ generated documentation, and conversions to other media types.
34
+
35
+ “Work” shall mean the work of authorship, whether in Source or Object form, made
36
+ available under the License, as indicated by a copyright notice that is included
37
+ in or attached to the work (an example is provided in the Appendix below).
38
+
39
+ “Derivative Works” shall mean any work, whether in Source or Object form, that
40
+ is based on (or derived from) the Work and for which the editorial revisions,
41
+ annotations, elaborations, or other modifications represent, as a whole, an
42
+ original work of authorship. For the purposes of this License, Derivative Works
43
+ shall not include works that remain separable from, or merely link (or bind by
44
+ name) to the interfaces of, the Work and Derivative Works thereof.
45
+
46
+ “Contribution” shall mean any work of authorship, including the original version
47
+ of the Work and any modifications or additions to that Work or Derivative Works
48
+ thereof, that is intentionally submitted to Licensor for inclusion in the Work
49
+ by the copyright owner or by an individual or Legal Entity authorized to submit
50
+ on behalf of the copyright owner. For the purposes of this definition,
51
+ “submitted” means any form of electronic, verbal, or written communication sent
52
+ to the Licensor or its representatives, including but not limited to
53
+ communication on electronic mailing lists, source code control systems, and
54
+ issue tracking systems that are managed by, or on behalf of, the Licensor for
55
+ the purpose of discussing and improving the Work, but excluding communication
56
+ that is conspicuously marked or otherwise designated in writing by the copyright
57
+ owner as “Not a Contribution.”
58
+
59
+ “Contributor” shall mean Licensor and any individual or Legal Entity on behalf
60
+ of whom a Contribution has been received by Licensor and subsequently
61
+ incorporated within the Work.
62
+
63
+ #### 2. Grant of Copyright License
64
+
65
+ Subject to the terms and conditions of this License, each Contributor hereby
66
+ grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
67
+ irrevocable copyright license to reproduce, prepare Derivative Works of,
68
+ publicly display, publicly perform, sublicense, and distribute the Work and such
69
+ Derivative Works in Source or Object form.
70
+
71
+ #### 3. Grant of Patent License
72
+
73
+ Subject to the terms and conditions of this License, each Contributor hereby
74
+ grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
75
+ irrevocable (except as stated in this section) patent license to make, have
76
+ made, use, offer to sell, sell, import, and otherwise transfer the Work, where
77
+ such license applies only to those patent claims licensable by such Contributor
78
+ that are necessarily infringed by their Contribution(s) alone or by combination
79
+ of their Contribution(s) with the Work to which such Contribution(s) was
80
+ submitted. If You institute patent litigation against any entity (including a
81
+ cross-claim or counterclaim in a lawsuit) alleging that the Work or a
82
+ Contribution incorporated within the Work constitutes direct or contributory
83
+ patent infringement, then any patent licenses granted to You under this License
84
+ for that Work shall terminate as of the date such litigation is filed.
85
+
86
+ #### 4. Redistribution
87
+
88
+ You may reproduce and distribute copies of the Work or Derivative Works thereof
89
+ in any medium, with or without modifications, and in Source or Object form,
90
+ provided that You meet the following conditions:
91
+
92
+ * **(a)** You must give any other recipients of the Work or Derivative Works a copy of
93
+ this License; and
94
+ * **(b)** You must cause any modified files to carry prominent notices stating that You
95
+ changed the files; and
96
+ * **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
97
+ all copyright, patent, trademark, and attribution notices from the Source form
98
+ of the Work, excluding those notices that do not pertain to any part of the
99
+ Derivative Works; and
100
+ * **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
101
+ Derivative Works that You distribute must include a readable copy of the
102
+ attribution notices contained within such NOTICE file, excluding those notices
103
+ that do not pertain to any part of the Derivative Works, in at least one of the
104
+ following places: within a NOTICE text file distributed as part of the
105
+ Derivative Works; within the Source form or documentation, if provided along
106
+ with the Derivative Works; or, within a display generated by the Derivative
107
+ Works, if and wherever such third-party notices normally appear. The contents of
108
+ the NOTICE file are for informational purposes only and do not modify the
109
+ License. You may add Your own attribution notices within Derivative Works that
110
+ You distribute, alongside or as an addendum to the NOTICE text from the Work,
111
+ provided that such additional attribution notices cannot be construed as
112
+ modifying the License.
113
+
114
+ You may add Your own copyright statement to Your modifications and may provide
115
+ additional or different license terms and conditions for use, reproduction, or
116
+ distribution of Your modifications, or for any such Derivative Works as a whole,
117
+ provided Your use, reproduction, and distribution of the Work otherwise complies
118
+ with the conditions stated in this License.
119
+
120
+ #### 5. Submission of Contributions
121
+
122
+ Unless You explicitly state otherwise, any Contribution intentionally submitted
123
+ for inclusion in the Work by You to the Licensor shall be under the terms and
124
+ conditions of this License, without any additional terms or conditions.
125
+ Notwithstanding the above, nothing herein shall supersede or modify the terms of
126
+ any separate license agreement you may have executed with Licensor regarding
127
+ such Contributions.
128
+
129
+ #### 6. Trademarks
130
+
131
+ This License does not grant permission to use the trade names, trademarks,
132
+ service marks, or product names of the Licensor, except as required for
133
+ reasonable and customary use in describing the origin of the Work and
134
+ reproducing the content of the NOTICE file.
135
+
136
+ #### 7. Disclaimer of Warranty
137
+
138
+ Unless required by applicable law or agreed to in writing, Licensor provides the
139
+ Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
140
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
141
+ including, without limitation, any warranties or conditions of TITLE,
142
+ NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
143
+ solely responsible for determining the appropriateness of using or
144
+ redistributing the Work and assume any risks associated with Your exercise of
145
+ permissions under this License.
146
+
147
+ #### 8. Limitation of Liability
148
+
149
+ In no event and under no legal theory, whether in tort (including negligence),
150
+ contract, or otherwise, unless required by applicable law (such as deliberate
151
+ and grossly negligent acts) or agreed to in writing, shall any Contributor be
152
+ liable to You for damages, including any direct, indirect, special, incidental,
153
+ or consequential damages of any character arising as a result of this License or
154
+ out of the use or inability to use the Work (including but not limited to
155
+ damages for loss of goodwill, work stoppage, computer failure or malfunction, or
156
+ any and all other commercial damages or losses), even if such Contributor has
157
+ been advised of the possibility of such damages.
158
+
159
+ #### 9. Accepting Warranty or Additional Liability
160
+
161
+ While redistributing the Work or Derivative Works thereof, You may choose to
162
+ offer, and charge a fee for, acceptance of support, warranty, indemnity, or
163
+ other liability obligations and/or rights consistent with this License. However,
164
+ in accepting such obligations, You may act only on Your own behalf and on Your
165
+ sole responsibility, not on behalf of any other Contributor, and only if You
166
+ agree to indemnify, defend, and hold each Contributor harmless for any liability
167
+ incurred by, or claims asserted against, such Contributor by reason of your
168
+ accepting any such warranty or additional liability.
169
+
170
+ _END OF TERMS AND CONDITIONS_
171
+
172
+ ### APPENDIX: How to apply the Apache License to your work
173
+
174
+ To apply the Apache License to your work, attach the following boilerplate
175
+ notice, with the fields enclosed by brackets `[]` replaced with your own
176
+ identifying information. (Don't include the brackets!) The text should be
177
+ enclosed in the appropriate comment syntax for the file format. We also
178
+ recommend that a file or class name and description of purpose be included on
179
+ the same “printed page” as the copyright notice for easier identification within
180
+ third-party archives.
181
+
182
+ Copyright [yyyy] [name of copyright owner]
183
+
184
+ Licensed under the Apache License, Version 2.0 (the "License");
185
+ you may not use this file except in compliance with the License.
186
+ You may obtain a copy of the License at
187
+
188
+ http://www.apache.org/licenses/LICENSE-2.0
189
+
190
+ Unless required by applicable law or agreed to in writing, software
191
+ distributed under the License is distributed on an "AS IS" BASIS,
192
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
193
+ See the License for the specific language governing permissions and
194
+ limitations under the License.
@@ -0,0 +1,101 @@
1
+ # BackPressure
2
+
3
+ The `back_pressure` gem provides a small set of tools for providing back-pressure in Ruby.
4
+
5
+ This project is designed to be zero-dependency and API-stable at `1.x`, making it a safe dependency that will not lead to transitive-dependency conflicts.
6
+
7
+ It is licensed under the [Apache License, Version 2.0](./LICENSE-APACHE2.md), which grants you the freedom to use and modify it and limits the liability of the contributors.
8
+
9
+ ## Status
10
+ - [![Build Status](https://travis-ci.org/yaauie/ruby_back_pressure.svg?branch=master)](https://travis-ci.org/yaauie/ruby_back_pressure)
11
+ - [![Apache License](https://img.shields.io/badge/license-Apache%202-green.svg)](https://www.rubydoc.info/github/yaauie/ruby_back_pressure/master)
12
+ - ![API: Stable](https://img.shields.io/badge/API-stable-green.svg)
13
+ - ![Runtime Dependencies: Zero](https://img.shields.io/badge/runtime%20dependencies-0-green.svg)
14
+
15
+
16
+ ## Installation
17
+
18
+ ### Using Bundler
19
+
20
+ Either this line to your application's Gemfile:
21
+
22
+ ~~~ruby
23
+ gem 'back_pressure', '~> 1.0'
24
+ ~~~
25
+
26
+ Or to your library project's `gemspec`:
27
+
28
+ ~~~ruby
29
+ spec.add_runtime_dependency 'back_pressure', '~> 1.0`
30
+ ~~~
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ ### Manual Installation
37
+
38
+ This gem can be installed manually with the `gem install` command:
39
+
40
+ $ gem install back_pressure
41
+
42
+ ## Usage
43
+
44
+ ### `BackPressure::Executor`
45
+
46
+ Implementations of `BackPressure::Executor` provide a way to execute a block of code once it is safe to do so.
47
+
48
+ #### Example: `BackPressure::GatedExecutor`
49
+
50
+ Suppose we have a non-blocking API client that will fire hooks when its underlying connection is blocked (e.g., on a two-way stream when the receiver indicates that it is temporarily unable or unwilling to read from the stream).
51
+ Continuing to push data to a non-blocking API in this state is dangerous, because if the connection is lost we are liable to lose data.
52
+ We can use a `GatedExecutor` to ensure that we propagate the blockage to the code that is attempting to push to the non-blocking API client:
53
+
54
+ ~~~ruby
55
+ gated_executor = BackPressure::GatedExecutor.new
56
+
57
+ non_blocking_api_client.on_connection_blocked { gated_executor.engage_back_pressure }
58
+ non_blocking_api_client.on_connection_unblocked { gated_executor.remove_back_pressure }
59
+
60
+ 16.times do
61
+ Thread.new do
62
+ loop do
63
+ message = queue.pop
64
+ gated_executor.execute { non_blocking_api_client.push(message) }
65
+ end
66
+ end
67
+ end
68
+ ~~~
69
+
70
+ ## Development
71
+
72
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
73
+
74
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
75
+
76
+ ## Contributing
77
+
78
+ Bug reports and pull requests are welcome on GitHub at https://github.com/yaauie/ruby_back_pressure.
79
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
80
+
81
+ ### Project Priorities
82
+
83
+ Contributions to this project will be assessed by the following priorities, _in order_.
84
+
85
+ 0. **Zero Breaking Changes**: This project is designed to be API-stable, enabling users to add it as a dependency of their own projects without risk of introducing transitive-dependency conflicts. If a change is desired that cannot be implemented within the existing promises made, it _MUST_ be implemented distinctly and separately.
86
+ 1. **Zero Runtime Dependencies**: In order to eliminate transitive-dependency conflicts, contributions _MUST NOT_ introduce runtime dependencies.
87
+ 2. **Zero Mutation**: Tools in this project _MUST NOT_ mutate any objects that they do not explicitly own.
88
+ 3. **Clarity and Validation of Promises Made**: In order to ensure future development doesn't accidentally break real-world usage, contributions _MUST_ clearly and explicitly document and verify the promises they make.
89
+ 4. **Ease of Use**: In order to ensure that users can safely consume the tools provided by this gem, usage patterns _SHOULD_ be clear and concise.
90
+ 5. **Composable Components over Complete Solutions**: In order to provide API-stability, tools _SHOULD_ be implemented in their simplest possible form.
91
+
92
+ ### Versioning
93
+
94
+ This project follows the [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard, and is API-stable at `1.x`:
95
+ - MAJOR: will always be 1.x, since this project is API-stable by design.
96
+ - MINOR: new backward-compatible features and abstractions will be available in minor releases.
97
+ - PATCH: fixes to existing features will be made available in patch-level releases.
98
+
99
+ ## Code of Conduct
100
+
101
+ Everyone interacting in the BackPressure project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/back_pressure/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,38 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "back_pressure/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "back_pressure"
8
+ spec.version = BackPressure::VERSION
9
+ spec.authors = ["Ry Biesemeyer"]
10
+ spec.email = ["identity@yaauie.com"]
11
+ spec.license = 'Apache-2.0'
12
+
13
+ spec.summary = %q{Tools for providing blocking back-pressure}
14
+ spec.description = %q{BackPressure is a zero-dependency collection of stable-API tools } +
15
+ %q{for safely and efficiently providing blocking back-pressure.}
16
+ spec.homepage = "https://github.com/yaauie/ruby_back_pressure"
17
+
18
+ spec.metadata = {
19
+ "source_code_uri" => "https://github.com/yaauie/ruby_back_pressure",
20
+ "bug_tracker_uri" => "https://github.com/yaauie/ruby_back_pressure/issues",
21
+ }
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.required_ruby_version = '>= 2.0'
33
+
34
+ spec.add_development_dependency "bundler", "~> 2.0"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_development_dependency "rspec", "~> 3.0"
37
+ spec.add_development_dependency "yard", "~> 0.9.20"
38
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "back_pressure"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ # Copyright 2019 Ry Biesemeyer <identity@yaauie.com>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require_relative "back_pressure/version"
18
+ require_relative "back_pressure/gated_executor"
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ # Copyright 2019 Ry Biesemeyer <identity@yaauie.com>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module BackPressure
18
+ ##
19
+ # @since 1.0.0
20
+ ExecutionExpired = Class.new(RuntimeError)
21
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+
3
+ # Copyright 2019 Ry Biesemeyer <identity@yaauie.com>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require_relative "execution_expired"
18
+
19
+ module BackPressure
20
+ ##
21
+ # Implementations of `BackPressure::Executor` are capable of providing
22
+ # blocking back-pressure to callers using their `execute` or `execute!`
23
+ # methods.
24
+ #
25
+ # This interface makes no guarantees about how implementations go about
26
+ # controlling back-pressure or the order in which execution will occur
27
+ # in the presence or absence of blocking back-pressure.
28
+ #
29
+ # @abstract
30
+ #
31
+ # @author Ry Biesemeyer <identity@yaauue.com>
32
+ # @since 1.0.0
33
+ class Executor
34
+ ##
35
+ # Executes the provided block, _after_ waiting out any back-pressure,
36
+ # returning `true` IFF the block was executed.
37
+ #
38
+ # @param blocking_time_limit [Number]: the maximum time to wait, in
39
+ # seconds, when back-pressure is
40
+ # being applied, before aborting
41
+ # (optional).
42
+ # @return [Boolean]: returns `true` if block was successfully executed,
43
+ # and `false` if tht `blocking_time_limit` was
44
+ # reached before it could be executed.
45
+ #
46
+ # @yieldreturn [void]: the value returned by the block is ignored by
47
+ # this method.
48
+ def execute(blocking_time_limit: nil)
49
+ fail NotImplementedError
50
+ end
51
+
52
+ ##
53
+ # Executes the provided block, _after_ waiting out any back-pressure,
54
+ # returning the result of the block or raising an `ExecutionExpired`
55
+ # exception if the provided limit was reached before execution could
56
+ # begin.
57
+ #
58
+ # @param blocking_time_limit [Number]: the maximum time to wait, in
59
+ # seconds, when back-pressure is
60
+ # being applied, before aborting
61
+ # (optional).
62
+ # @return [Object]: returns the unmodified value of the result of
63
+ # executing the provided block.
64
+ # @raise [ExecutionExpired]
65
+ # @yieldreturn [Object]: the value returned from the block is returned
66
+ # by this method
67
+ def execute!(blocking_time_limit: nil)
68
+ fail NotImplementedError
69
+ end
70
+
71
+ ##
72
+ # Helper method for observing which threads, if any, are blocked at
73
+ # the instant the method is invoked. The returned value is a frozen
74
+ # snapshot, and the included threads are not guaranteed to be still
75
+ # blocking by the time they are accessed.
76
+ #
77
+ # @api observation
78
+ # @note This method should be used only for observation-based tooling.
79
+ #
80
+ # @return [Set{Thread}]
81
+ def blocked_threads
82
+ fail NotImplementedError
83
+ end
84
+
85
+ ##
86
+ # Helper method for determining if any threads are currently blocked
87
+ # by back-pressure.
88
+ #
89
+ # @api observation
90
+ # @note This method should be used only for observation-based tooling.
91
+ #
92
+ # @return [Boolean]
93
+ def blocked?
94
+ fail NotImplementedError
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,213 @@
1
+ # encoding: utf-8
2
+
3
+ # Copyright 2019 Ry Biesemeyer <identity@yaauie.com>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require_relative 'executor'
18
+
19
+ module BackPressure
20
+ ##
21
+ # A {GatedExecutor} is an implementation of {BackPressure::Executor} that
22
+ # allows external control of back-pressure state, and is useful when
23
+ # non-blocking APIs provide hooks for identifying when they _should_ block.
24
+ #
25
+ # @author Ry Biesemeyer <identity@yaauie.com>
26
+ # @since 1.0.0
27
+ #
28
+ # @example Using a GatedExecutor with a non-blocking API
29
+ # gated_executor = BackPressure::GatedExecutor.new
30
+ #
31
+ # non_blocking_api_client.on_connection_blocked { gated_executor.engage_back_pressure }
32
+ # non_blocking_api_client.on_connection_unblocked { gated_executor.remove_back_pressure }
33
+ #
34
+ # 16.times do
35
+ # Thread.new do
36
+ # loop do
37
+ # message = queue.pop
38
+ # gated_executor.execute { non_blocking_api_client.push(message) }
39
+ # end
40
+ # end
41
+ # end
42
+ class GatedExecutor < Executor
43
+
44
+ DEFAULT_REASON = "reason not given".freeze
45
+ private_constant :DEFAULT_REASON
46
+
47
+ ##
48
+ # @param logger [Logger]: logger on which to emit (optional)
49
+ # @param description [String]: description for logs (optional)
50
+ # @param log_threshold [Number]: silences blockage warnings for durations
51
+ # less than the provided value
52
+ # (default `0`).
53
+ #
54
+ # @yield [gated_executor] if a block is provided, the newly-created
55
+ # instance is yielded to the given block after
56
+ # being initialised.
57
+ # @yieldparam [self]
58
+ # @yieldreturn [void]
59
+ def initialize(logger: nil,
60
+ description: nil,
61
+ log_threshold: 1)
62
+
63
+ @logger = logger
64
+ @desc = (description ? description.dup : "#{self.class.name}<#{__id__}>").freeze
65
+ @log_threshold = log_threshold
66
+
67
+ @control_mutex = Mutex.new
68
+ @control_condv = ConditionVariable.new
69
+
70
+ @blocked_threads = Set.new
71
+ @blocked_threads_mutex = Mutex.new
72
+
73
+ yield(self) if block_given?
74
+ end
75
+
76
+ ##
77
+ # Engages back-pressure and immediately returns; threads that send this
78
+ # instance `GatedExecutor#execute` will be blocked until back-pressure is
79
+ # removed.
80
+ #
81
+ # @param reason [String]: the reason back-pressure is being applied, to be
82
+ # included in the log message (optional).
83
+ # @return [void]
84
+ def engage_back_pressure(reason=DEFAULT_REASON)
85
+ @control_mutex.synchronize do
86
+ if !@back_pressure_engaged
87
+ @back_pressure_engaged = true
88
+ @logger && @logger.info("#{@desc} back-pressure engaged (#{reason})")
89
+ else
90
+ @logger && @logger.debug("#{@desc} attempted to engage back-pressure when it is already engaged (#{reason})")
91
+ end
92
+ end
93
+ end
94
+
95
+ ##
96
+ # Removes back-pressure, waking any threads that are currently blocked
97
+ # by back-pressure, and immediately returns.
98
+ #
99
+ # @note No guarantee of ordering are made with regard to threads that
100
+ # are blocked at the instant back-pressure is removed.
101
+ #
102
+ # @return [void]
103
+ def remove_back_pressure(reason=DEFAULT_REASON)
104
+ @control_mutex.synchronize do
105
+ if @back_pressure_engaged
106
+ @back_pressure_engaged = false
107
+ @logger && @logger.info("#{@desc} back-pressure removed (#{reason})")
108
+ @control_condv.broadcast # wakeup _all_ waiting threads
109
+ else
110
+ @logger && @logger.debug("#{@desc} attempted to remove back-pressure when it not engaged (#{reason})")
111
+ end
112
+ end
113
+ end
114
+
115
+ ##
116
+ # Helper method for determining if back-pressure is currently engaged.
117
+ #
118
+ # @return [Boolean]
119
+ def back_pressure_engaged?
120
+ @control_mutex.synchronize { @back_pressure_engaged }
121
+ end
122
+
123
+ ##
124
+ # (see Executor#execute)
125
+ #
126
+ # @note Care must be taken to ensure that back-pressure control is executed
127
+ # outside of this block, as the block provided is not executed while
128
+ # back-pressure is engaged.
129
+ def execute(blocking_time_limit=nil)
130
+ fail(ArgumentError, 'block required!') unless block_given?
131
+
132
+ if !@back_pressure_engaged || block_until_back_pressure_removed(blocking_time_limit)
133
+ yield
134
+ return true
135
+ else
136
+ return false
137
+ end
138
+ end
139
+
140
+ ##
141
+ # (see Executor#execute!)
142
+ #
143
+ # @note Care must be taken to ensure that back-pressure control is executed
144
+ # outside of this block, as the block provided is not executed while
145
+ # back-pressure is engaged.
146
+ def execute!(blocking_time_limit=nil)
147
+ execute(blocking_time_limit) do
148
+ return yield
149
+ end
150
+
151
+ fail(ExecutionExpired)
152
+ end
153
+
154
+ ##
155
+ # (see Executor#blocked_threads)
156
+ def blocked_threads
157
+ @blocked_threads_mutex.synchronize { @blocked_threads.dup.freeze }
158
+ end
159
+
160
+ ##
161
+ # (see Executor#blocked?)
162
+ def blocked?
163
+ blocked_threads.any?
164
+ end
165
+
166
+ private
167
+
168
+ ##
169
+ # Blocks while back-pressure is engaged, immediately unblocking all threads
170
+ # as soon as the back-pressure is removed.
171
+ #
172
+ # @api private
173
+ #
174
+ # @param blocking_time_limit [Number]: the maximum time to wait, in seconds,
175
+ # when back-pressure is being applied,
176
+ # before aborting (optional).
177
+ #
178
+ # @return [Boolean] returns `true` as soon as back-pressure is released,
179
+ # or `false` if a provided `blocking_time_limit` was
180
+ # reached before back-ressure could be released.
181
+ def block_until_back_pressure_removed(blocking_time_limit=nil)
182
+ @blocked_threads_mutex.synchronize { @blocked_threads.add(Thread.current) }
183
+
184
+ timeout = [0.5, blocking_time_limit].compact.min
185
+ start = Time.now
186
+ thread_id = Thread.current.to_s
187
+
188
+ loop do
189
+ should_block = @control_mutex.synchronize do
190
+ @control_condv.wait(@control_mutex, timeout)
191
+ @back_pressure_engaged
192
+ end
193
+ break unless should_block
194
+
195
+ block_duration = Time.now - start
196
+ if @logger && block_duration > @log_threshold
197
+ @logger.warn("#{@desc} has been blocked for #{block_duration.round(2)}s... (#{thread_id})")
198
+ end
199
+
200
+ if blocking_time_limit && block_duration > blocking_time_limit
201
+ @logger && @logger.warn("#{@desc} blocking back-pressure exceeded limit of #{blocking_time_limit}s (#{thread_id})")
202
+ return false
203
+ end
204
+
205
+ timeout = [30, (timeout * 2), blocking_time_limit && (blocking_time_limit-block_duration)].compact.min
206
+ end
207
+
208
+ return true
209
+ ensure
210
+ @blocked_threads_mutex.synchronize { @blocked_threads.delete(Thread.current) }
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ # Copyright 2019 Ry Biesemeyer <identity@yaauie.com>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module BackPressure
18
+ VERSION = "1.0.0"
19
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: back_pressure
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ry Biesemeyer
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-07-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.20
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.20
69
+ description: BackPressure is a zero-dependency collection of stable-API tools for
70
+ safely and efficiently providing blocking back-pressure.
71
+ email:
72
+ - identity@yaauie.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - CHANGELOG.md
81
+ - CODE_OF_CONDUCT.md
82
+ - Gemfile
83
+ - LICENSE-APACHE2.md
84
+ - README.md
85
+ - Rakefile
86
+ - back_pressure.gemspec
87
+ - bin/console
88
+ - bin/setup
89
+ - lib/back_pressure.rb
90
+ - lib/back_pressure/execution_expired.rb
91
+ - lib/back_pressure/executor.rb
92
+ - lib/back_pressure/gated_executor.rb
93
+ - lib/back_pressure/version.rb
94
+ homepage: https://github.com/yaauie/ruby_back_pressure
95
+ licenses:
96
+ - Apache-2.0
97
+ metadata:
98
+ source_code_uri: https://github.com/yaauie/ruby_back_pressure
99
+ bug_tracker_uri: https://github.com/yaauie/ruby_back_pressure/issues
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '2.0'
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 2.7.7
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Tools for providing blocking back-pressure
120
+ test_files: []