Skip to content

Commit

Permalink
Version 0.2.0
Browse files Browse the repository at this point in the history
Added #then for promise-style resolution of values
  • Loading branch information
rahoulb committed Oct 13, 2023
1 parent bd6c79c commit b561096
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
standard-procedure-async (0.1.3)
standard-procedure-async (0.2.0)
concurrent-ruby (>= 1.0)

GEM
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,22 @@ This queueing has an important implication.
If you are within an actor class, you can happily call async methods on other objects and `await` the results - because each actor has its own independent queue. But if you are calling your own methods, you must **never** call `await` (or call `value` on the returned message object).

Picture this:

```ruby
class ClassThatWillDeadlock
include StandardProcedure::Async::Actor

async :start_processing do
do_some_complicated_calculation.value
end

async :do_some_complicated_calculation do
sleep 1
2 + 2
end
end
```

- Your actor has two `async` methods - `start_processing` and `do_complicated_calculation`
- The main thread calls `start_processing` - so a message gets added to the head of the queue.
- The actor's internal thread starts working on the message at the head of the queue, and `_start_processing` (the actual implementation) is called.
Expand All @@ -175,6 +191,22 @@ Picture this:

To avoid this, if you ever need the return value from an internal `async` method, always call the implementation method: `_do_complicated_calculation` instead of `do_complicated_calculation`. This does not put anything on the queue and proceeds as a normal method call.

```ruby
class ClassThatWillNotDeadlock
include StandardProcedure::Async::Actor

async :start_processing do
_do_some_complicated_calculation
end

async :do_some_complicated_calculation do
sleep 1
2 + 2
end
end
```


As a fail-safe, any calls to `value` will also time-out after 30 seconds, returning Concurrent::MVar::TIMEOUT. If you need to override the timeout value you can use `message.value(timeout: value_in_seconds)`.

### The rules of using actors
Expand Down
1 change: 1 addition & 0 deletions checksums/standard-procedure-async-0.2.0.gem.sha512
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
e9ecad84981421f13a69b6fa77964a1331488ddeb9581dcb2381d7daa4f6ae712f4a33edd1291b50fb70a49896a14b9b84dc6df4001a5c834ed561173685ba30
3 changes: 0 additions & 3 deletions lib/standard_procedure/async/actor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ def value(timeout: 30)
alias_method :await, :value

def then &block
puts "in then"
block&.call value
puts "called handler"
return self
end

def call
Expand Down
12 changes: 0 additions & 12 deletions lib/standard_procedure/async/await.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,4 @@ module Kernel
def await &block
block.call.value
end

def wait_for timeout = 30, &block
puts "WAITING"
timeout = timeout * 10
counter = 0
while !block.call do
puts "...waiting"
sleep 0.1
counter += 1
raise "Timeout" if counter >= timeout
end
end
end
2 changes: 1 addition & 1 deletion lib/standard_procedure/async/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

module StandardProcedure
module Async
VERSION = "0.1.3"
VERSION = "0.2.0"
end
end
80 changes: 37 additions & 43 deletions spec/standard_procedure/async/actor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,74 +41,68 @@
# test that do_something actually ran in a different thread
expect(other_thread).not_to eq current_thread
end
it "accesses the result of an asynchronous method using #value" do
klass = Class.new do

it "accesses the result of an asynchronous method using #value" do
klass = Class.new do
include StandardProcedure::Async::Actor

async :say_hello do
sleep 0.1
"Hello"
end
end

expect(klass.new.say_hello.value).to eq "Hello"
end

it "accesses the result of an asynchronous method using #await" do
klass = Class.new do
it "accesses the result of an asynchronous method using #await" do
klass = Class.new do
include StandardProcedure::Async::Actor

async :say_hello do
sleep 0.1
"Hello"
end
end

expect(klass.new.say_hello.await).to eq "Hello"
end

it "accesses the result of an asynchronous method using #get" do
klass = Class.new do
it "accesses the result of an asynchronous method using #get" do
klass = Class.new do
include StandardProcedure::Async::Actor

async :say_hello do
sleep 0.1
"Hello"
end
end

expect(klass.new.say_hello.get).to eq "Hello"
end

# it "accesses the result of an asynchronous method using #then, yielding the result in the caller's thread" do
# current_thread = Thread.current.to_s
# other_thread = nil
# handler_thread = nil
#
# klass = Class.new do
# include StandardProcedure::Async::Actor
#
# async :do_something do
# puts "doing something"
# sleep 2
# puts "done it"
# Thread.current.to_s
# end
# end
#
# puts Thread.current.to_s
# klass.new.do_something.then do |result|
# puts "in handler"
# puts Thread.current.to_s
# other_thread = result
# handler_thread = Thread.current.to_s
# puts "handler is done #{other_thread}"
# end
#
# expect(other_thread).to_not eq current_thread
# expect(handler_thread).to eq current_thread
# end
it "accesses the result of an asynchronous method using #then, yielding the result in the caller's thread" do
current_thread = Thread.current.to_s
other_thread = nil
handler_thread = nil

klass = Class.new do
include StandardProcedure::Async::Actor

async :do_something do
sleep 1
Thread.current.to_s
end
end

klass.new.do_something.then do |result|
other_thread = result
handler_thread = Thread.current.to_s
end

expect(other_thread).to_not eq current_thread
expect(handler_thread).to eq current_thread
end

it "adds multiple messages to a queue and performs them in order" do
things = Concurrent::Array.new
Expand All @@ -117,7 +111,7 @@
include StandardProcedure::Async::Actor

async :do_something do |number|
sleep(0.3) if number % 2 == 0
sleep(rand)
things << number
:done
end
Expand All @@ -138,7 +132,7 @@
include StandardProcedure::Async::Actor

async :wait_for_ages do
sleep 10 # this will never finish
sleep 10 # this will never finish because we set the timeout to 1
"You will never see this"
end
end
Expand Down

0 comments on commit b561096

Please sign in to comment.