Skip to content

Commit ee511f2

Browse files
committed
fix headers, add cancellation and throttling to documetnation
1 parent 773ad59 commit ee511f2

File tree

2 files changed

+128
-45
lines changed

2 files changed

+128
-45
lines changed

doc/promises.in.md

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# Promises Framework
2-
31
Promises is a new framework unifying former `Concurrent::Future`,
42
`Concurrent::Promise`, `Concurrent::IVar`, `Concurrent::Event`,
53
`Concurrent.dataflow`, `Delay`, and `TimerTask`. It extensively uses the new
@@ -8,16 +6,14 @@ with the exception of obviously blocking operations like
86
`#wait`, `#value`, etc. As a result it lowers a danger of deadlocking and offers
97
better performance.
108

11-
## Overview
12-
139
*TODO*
1410

1511
- What is it?
1612
- What is it for?
1713
- Main classes {Future}, {Event}
1814
- Explain pool usage :io vs :fast, and `_on` `_using` suffixes.
1915

20-
## Old examples follow
16+
# Old examples
2117

2218
*TODO review pending*
2319

@@ -219,7 +215,7 @@ Factory methods are taking names of the global executors
219215
# executed on :fast executor, only short and non-blocking tasks can go there
220216
future_on(:fast) { 2 }.
221217
# executed on executor for blocking and long operations
222-
then_using(:io) { File.read __FILE__ }.
218+
then_on(:io) { File.read __FILE__ }.
223219
wait
224220
```
225221

@@ -239,22 +235,22 @@ future { 2 }.
239235
actor.ask(2).then(&:succ).value
240236
```
241237

242-
### Common use-cases Examples
238+
# Common use-cases Examples
243239

244-
#### simple background processing
240+
## simple background processing
245241

246242
```ruby
247243
future { do_stuff }
248244
```
249245

250-
#### parallel background processing
246+
## parallel background processing
251247

252248
```ruby
253249
jobs = 10.times.map { |i| future { i } } #
254250
zip(*jobs).value
255251
```
256252

257-
#### periodic task
253+
## periodic task
258254

259255
```ruby
260256
def schedule_job(interval, &job)
@@ -295,7 +291,7 @@ arr, v = [], nil; arr << v while (v = queue.pop) #
295291
# arr has the results from the executed scheduled tasks
296292
arr
297293
```
298-
#### How to limit processing where there are limited resources?
294+
## How to limit processing where there are limited resources?
299295

300296
By creating an actor managing the resource
301297

@@ -348,3 +344,41 @@ end #
348344

349345
zip(*concurrent_jobs).value!
350346
```
347+
348+
# Experimental
349+
350+
## Cancellation
351+
352+
```ruby
353+
source, token = Concurrent::Cancellation.create
354+
355+
futures = Array.new(2) do
356+
future(token) do |token|
357+
token.loop_until_canceled { Thread.pass }
358+
:done
359+
end
360+
end
361+
362+
sleep 0.05
363+
source.cancel
364+
futures.map(&:value!)
365+
```
366+
367+
## Throttling
368+
369+
```ruby
370+
data = (0..10).to_a
371+
max_tree = Concurrent::Throttle.new 3
372+
373+
futures = data.map do |data|
374+
future(data) do |data|
375+
# un-throttled
376+
data + 1
377+
end.throttle(max_tree) do |trigger|
378+
# throttled, imagine it uses DB connections or other limited resource
379+
trigger.then { |v| v * 2 * 2 }
380+
end
381+
end #
382+
383+
futures.map(&:value!)
384+
```

doc/promises.out.md

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# Promises Framework
2-
31
Promises is a new framework unifying former `Concurrent::Future`,
42
`Concurrent::Promise`, `Concurrent::IVar`, `Concurrent::Event`,
53
`Concurrent.dataflow`, `Delay`, and `TimerTask`. It extensively uses the new
@@ -8,16 +6,14 @@ with the exception of obviously blocking operations like
86
`#wait`, `#value`, etc. As a result it lowers a danger of deadlocking and offers
97
better performance.
108

11-
## Overview
12-
139
*TODO*
1410

1511
- What is it?
1612
- What is it for?
1713
- Main classes {Future}, {Event}
1814
- Explain pool usage :io vs :fast, and `_on` `_using` suffixes.
1915

20-
## Old examples follow
16+
# Old examples
2117

2218
*TODO review pending*
2319

@@ -66,16 +62,16 @@ Class.new do
6662
resolvable_event
6763
end
6864
end.new.a_method
69-
# => <#Concurrent::Promises::ResolvableEvent:0x7fbb023df1e8 pending blocks:[]>
65+
# => <#Concurrent::Promises::ResolvableEvent:0x7fc5b1b085c8 pending blocks:[]>
7066

7167
Module.new { extend Concurrent::Promises::FactoryMethods }.resolvable_event
72-
# => <#Concurrent::Promises::ResolvableEvent:0x7fbb023d7600 pending blocks:[]>
68+
# => <#Concurrent::Promises::ResolvableEvent:0x7fc5b1b02088 pending blocks:[]>
7369
```
7470
The module is already extended into {Promises} for convenience.
7571

7672
```ruby
7773
Concurrent::Promises.resolvable_event
78-
# => <#Concurrent::Promises::ResolvableEvent:0x7fbb023d5ad0 pending blocks:[]>
74+
# => <#Concurrent::Promises::ResolvableEvent:0x7fc5b1afac48 pending blocks:[]>
7975
```
8076

8177
For this guide we include the module into `main` so we can call the factory
@@ -84,7 +80,7 @@ methods in following examples directly.
8480
```ruby
8581
include Concurrent::Promises::FactoryMethods
8682
resolvable_event
87-
# => <#Concurrent::Promises::ResolvableEvent:0x7fbb023cf608 pending blocks:[]>
83+
# => <#Concurrent::Promises::ResolvableEvent:0x7fc5b1af8830 pending blocks:[]>
8884
```
8985

9086
Simple asynchronous task:
@@ -101,7 +97,7 @@ Rejecting asynchronous task:
10197

10298
```ruby
10399
future = future { raise 'Boom' }
104-
# => <#Concurrent::Promises::Future:0x7fbb023b4308 pending blocks:[]>
100+
# => <#Concurrent::Promises::Future:0x7fc5b1ad9700 pending blocks:[]>
105101
future.value # => nil
106102
future.value! rescue $! # => #<RuntimeError: Boom>
107103
future.reason # => #<RuntimeError: Boom>
@@ -113,9 +109,9 @@ Direct creation of resolved futures:
113109

114110
```ruby
115111
fulfilled_future(Object.new)
116-
# => <#Concurrent::Promises::Future:0x7fbb023a58a8 fulfilled blocks:[]>
112+
# => <#Concurrent::Promises::Future:0x7fc5b1acaa70 fulfilled blocks:[]>
117113
rejected_future(StandardError.new("boom"))
118-
# => <#Concurrent::Promises::Future:0x7fbb023a79a0 rejected blocks:[]>
114+
# => <#Concurrent::Promises::Future:0x7fc5b1ac97b0 rejected blocks:[]>
119115
```
120116

121117
Chaining of futures:
@@ -155,7 +151,7 @@ fulfilled_future(Object.new).then(&:succ).rescue { 1 }.then(&:succ).value # resc
155151
fulfilled_future(1).then(&:succ).rescue { |e| e.message }.then(&:succ).value # no error, rescue not applied
156152

157153
rejected_zip = fulfilled_future(1) & rejected_future(StandardError.new('boom'))
158-
# => <#Concurrent::Promises::Future:0x7fbb023343b0 rejected blocks:[]>
154+
# => <#Concurrent::Promises::Future:0x7fc5b3051380 rejected blocks:[]>
159155
rejected_zip.result
160156
# => [false, [1, nil], [nil, #<StandardError: boom>]]
161157
rejected_zip.then { |v| 'never happens' }.result
@@ -177,13 +173,13 @@ future.value
177173
It propagates trough chain allowing whole or partial lazy chains.
178174
```ruby
179175
head = delay { 1 }
180-
# => <#Concurrent::Promises::Future:0x7fbb02304b38 pending blocks:[]>
176+
# => <#Concurrent::Promises::Future:0x7fc5b3021450 pending blocks:[]>
181177
branch1 = head.then(&:succ)
182-
# => <#Concurrent::Promises::Future:0x7fbb022fe328 pending blocks:[]>
178+
# => <#Concurrent::Promises::Future:0x7fc5b301b398 pending blocks:[]>
183179
branch2 = head.delay.then(&:succ)
184-
# => <#Concurrent::Promises::Future:0x7fbb03867d68 pending blocks:[]>
180+
# => <#Concurrent::Promises::Future:0x7fc5b30190c0 pending blocks:[]>
185181
join = branch1 & branch2
186-
# => <#Concurrent::Promises::Future:0x7fbb03865fe0 pending blocks:[]>
182+
# => <#Concurrent::Promises::Future:0x7fc5b30138f0 pending blocks:[]>
187183

188184
sleep 0.1 # nothing will resolve
189185
[head, branch1, branch2, join].map(&:resolved?)
@@ -217,14 +213,14 @@ Scheduling of asynchronous tasks:
217213

218214
# it'll be executed after 0.1 seconds
219215
scheduled = schedule(0.1) { 1 }
220-
# => <#Concurrent::Promises::Future:0x7fbb022a5570 pending blocks:[]>
216+
# => <#Concurrent::Promises::Future:0x7fc5b1a2a7f0 pending blocks:[]>
221217

222218
scheduled.resolved? # => false
223219
scheduled.value # available after 0.1sec
224220

225221
# and in chain
226222
scheduled = delay { 1 }.schedule(0.1).then(&:succ)
227-
# => <#Concurrent::Promises::Future:0x7fbb022948b0 pending blocks:[]>
223+
# => <#Concurrent::Promises::Future:0x7fc5b1a19a18 pending blocks:[]>
228224
# will not be scheduled until value is requested
229225
sleep 0.1
230226
scheduled.value # returns after another 0.1sec
@@ -235,21 +231,21 @@ Resolvable Future and Event:
235231
```ruby
236232

237233
future = resolvable_future
238-
# => <#Concurrent::Promises::ResolvableFuture:0x7fbb0223c1b0 pending blocks:[]>
234+
# => <#Concurrent::Promises::ResolvableFuture:0x7fc5b19c17a0 pending blocks:[]>
239235
event = resolvable_event()
240-
# => <#Concurrent::Promises::ResolvableEvent:0x7fbb021df0c8 pending blocks:[]>
236+
# => <#Concurrent::Promises::ResolvableEvent:0x7fc5b19c0468 pending blocks:[]>
241237

242238
# These threads will be blocked until the future and event is resolved
243239
t1 = Thread.new { future.value }
244240
t2 = Thread.new { event.wait }
245241

246242
future.fulfill 1
247-
# => <#Concurrent::Promises::ResolvableFuture:0x7fbb0223c1b0 fulfilled blocks:[]>
243+
# => <#Concurrent::Promises::ResolvableFuture:0x7fc5b19c17a0 fulfilled blocks:[]>
248244
future.fulfill 1 rescue $!
249245
# => #<Concurrent::MultipleAssignmentError: Future can be resolved only once. Current result is [true, 1, nil], trying to set [true, 1, nil]>
250246
future.fulfill 2, false # => false
251247
event.resolve
252-
# => <#Concurrent::Promises::ResolvableEvent:0x7fbb021df0c8 fulfilled blocks:[]>
248+
# => <#Concurrent::Promises::ResolvableEvent:0x7fc5b19c0468 fulfilled blocks:[]>
253249

254250
# The threads can be joined now
255251
[t1, t2].each &:join
@@ -258,9 +254,9 @@ event.resolve
258254
Callbacks:
259255

260256
```ruby
261-
queue = Queue.new # => #<Thread::Queue:0x007fbb021b63d0>
257+
queue = Queue.new # => #<Thread::Queue:0x007fc5b193b880>
262258
future = delay { 1 + 1 }
263-
# => <#Concurrent::Promises::Future:0x7fbb021b4fd0 pending blocks:[]>
259+
# => <#Concurrent::Promises::Future:0x7fc5b193a9a8 pending blocks:[]>
264260

265261
future.on_fulfillment { queue << 1 } # evaluated asynchronously
266262
future.on_fulfillment! { queue << 2 } # evaluated on resolving thread
@@ -278,7 +274,7 @@ Factory methods are taking names of the global executors
278274
# executed on :fast executor, only short and non-blocking tasks can go there
279275
future_on(:fast) { 2 }.
280276
# executed on executor for blocking and long operations
281-
then_using(:io) { File.read __FILE__ }.
277+
then_on(:io) { File.read __FILE__ }.
282278
wait
283279
```
284280

@@ -288,7 +284,7 @@ Interoperability with actors:
288284
actor = Concurrent::Actor::Utils::AdHoc.spawn :square do
289285
-> v { v ** 2 }
290286
end
291-
# => #<Concurrent::Actor::Reference:0x7fbb02104310 /square (Concurrent::Actor::Utils::AdHoc)>
287+
# => #<Concurrent::Actor::Reference:0x7fc5b18a37b0 /square (Concurrent::Actor::Utils::AdHoc)>
292288

293289

294290
future { 2 }.
@@ -299,23 +295,23 @@ future { 2 }.
299295
actor.ask(2).then(&:succ).value # => 5
300296
```
301297

302-
### Common use-cases Examples
298+
# Common use-cases Examples
303299

304-
#### simple background processing
300+
## simple background processing
305301

306302
```ruby
307303
future { do_stuff }
308-
# => <#Concurrent::Promises::Future:0x7fbb020bfe40 pending blocks:[]>
304+
# => <#Concurrent::Promises::Future:0x7fc5b186b4f0 pending blocks:[]>
309305
```
310306

311-
#### parallel background processing
307+
## parallel background processing
312308

313309
```ruby
314310
jobs = 10.times.map { |i| future { i } }
315311
zip(*jobs).value # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
316312
```
317313

318-
#### periodic task
314+
## periodic task
319315

320316
```ruby
321317
def schedule_job(interval, &job)
@@ -332,7 +328,7 @@ def schedule_job(interval, &job)
332328
end
333329
end
334330

335-
queue = Queue.new # => #<Thread::Queue:0x007fbb01065658>
331+
queue = Queue.new # => #<Thread::Queue:0x007fc5b10a9730>
336332
count = 0 # => 0
337333
interval = 0.05 # small just not to delay execution of this example
338334

@@ -356,7 +352,7 @@ arr, v = [], nil; arr << v while (v = queue.pop)
356352
# arr has the results from the executed scheduled tasks
357353
arr # => [0, 1, 2, 3]
358354
```
359-
#### How to limit processing where there are limited resources?
355+
## How to limit processing where there are limited resources?
360356

361357
By creating an actor managing the resource
362358

@@ -421,3 +417,56 @@ end
421417
zip(*concurrent_jobs).value!
422418
# => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "undefined method `size' for nil:NilClass"]
423419
```
420+
421+
# Experimental
422+
423+
## Cancellation
424+
425+
```ruby
426+
source, token = Concurrent::Cancellation.create
427+
# => [#<Concurrent::Cancellation:0x007fc5b19c1390
428+
# @Cancel=
429+
# <#Concurrent::Promises::ResolvableEvent:0x7fc5b19c1688 pending blocks:[<#Concurrent::Promises::EventWrapperPromise:0x7fc5b19c1250 pending>]>,
430+
# @ResolveArgs=[],
431+
# @Token=
432+
# #<Concurrent::Cancellation::Token:0x007fc5b19c0e18
433+
# @Cancel=<#Concurrent::Promises::Event:0x7fc5b19c11d8 pending blocks:[]>>>,
434+
# #<Concurrent::Cancellation::Token:0x007fc5b19c0e18
435+
# @Cancel=<#Concurrent::Promises::Event:0x7fc5b19c11d8 pending blocks:[]>>]
436+
437+
futures = Array.new(2) do
438+
future(token) do |token|
439+
token.loop_until_canceled { Thread.pass }
440+
:done
441+
end
442+
end
443+
# => [<#Concurrent::Promises::Future:0x7fc5b1938ef0 pending blocks:[]>,
444+
# <#Concurrent::Promises::Future:0x7fc5b0a1f860 pending blocks:[]>]
445+
446+
sleep 0.05 # => 0
447+
source.cancel # => true
448+
futures.map(&:value!) # => [:done, :done]
449+
```
450+
451+
## Throttling
452+
453+
```ruby
454+
data = (0..10).to_a # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
455+
max_tree = Concurrent::Throttle.new 3
456+
# => #<Concurrent::Throttle:0x007fc5b1888e10
457+
# @AtomicCanRun=#<Concurrent::AtomicReference:0x007fc5b1888de8>,
458+
# @Queue=#<Thread::Queue:0x007fc5b1888dc0>>
459+
460+
futures = data.map do |data|
461+
future(data) do |data|
462+
# un-throttled
463+
data + 1
464+
end.throttle(max_tree) do |trigger|
465+
# throttled, imagine it uses DB connections or other limited resource
466+
trigger.then { |v| v * 2 * 2 }
467+
end
468+
end
469+
470+
futures.map(&:value!)
471+
# => [4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44]
472+
```

0 commit comments

Comments
 (0)