Skip to content

Commit

Permalink
feat: add supports for timeouts
Browse files Browse the repository at this point in the history
improved code gen by not initializing `Generic`
  • Loading branch information
stakach committed Jan 7, 2020
1 parent 611e038 commit bebcdcc
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 15 deletions.
6 changes: 5 additions & 1 deletion shard.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: promise
version: 2.0.1
version: 2.1.0

dependencies:
tasker:
github: spider-gazelle/tasker

development_dependencies:
ameba:
Expand Down
23 changes: 23 additions & 0 deletions spec/timeout_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require "spec"
require "../src/promise"

describe "promise timeouts" do
it "should timeout a promise" do
p = Promise.new(Symbol, timeout: 2.milliseconds)
expect_raises(Promise::Timeout) { p.get }
end

it "should timeout a defer" do
p = Promise.defer(timeout: 1.millisecond) { sleep 1; "p1 wins" }
expect_raises(Promise::Timeout) { p.get }
end

it "should timeout a race" do
expect_raises(Promise::Timeout) do
Promise.race(
Promise.defer(timeout: 1.millisecond) { sleep 1; "p1 wins" },
Promise.defer { sleep 1; "p2 wins" }
).get
end
end
end
42 changes: 34 additions & 8 deletions src/promise.cr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require "tasker"

abstract class Promise
class Generic(Output)
macro get_type_var
Expand All @@ -15,8 +17,38 @@ abstract class Promise
end
end

macro new(type)
::Promise::DeferredPromise({{type.id}}).new
class Timeout < Exception
end

macro new(type, timeout = nil)
{% if timeout %}
begin
%promise = ::Promise::DeferredPromise({{type.id}}).new
%task = Tasker.instance.in({{timeout}}) { %promise.reject(::Promise::Timeout.new("operation timeout")) }
%promise.finally { %task.cancel }
%promise
end
{% else %}
::Promise::DeferredPromise({{type.id}}).new
{% end %}
end

# Execute code in the next tick of the event loop
# and return a promise for obtaining the value
macro defer(same_thread = false, timeout = nil, &block)
begin
%promise = ::Promise::ImplicitDefer.new({{same_thread}}) {
{{block.body}}
}.execute!

{% if timeout %}
%task = Tasker.instance.in({{timeout}}) { %promise.reject(::Promise::Timeout.new("operation timeout")) }
%promise.finally { |err| %task.cancel unless err.is_a?(::Promise::Timeout) }
%promise
{% end %}

%promise
end
end

macro reject(type, reason)
Expand All @@ -42,12 +74,6 @@ abstract class Promise
::Promise::ResolvedPromise.new(value)
end

# Execute code in the next tick of the event loop
# and return a promise for obtaining the value
def self.defer(same_thread = false, &block : -> _)
ImplicitDefer.new(same_thread, &block).execute!
end

macro map(collection, same_thread = false, &block)
%promise_collection = {{collection}}.map do |{{*block.args}}|
::Promise.defer(same_thread: {{same_thread}}) do
Expand Down
3 changes: 1 addition & 2 deletions src/promise/deferred_promise.cr
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ class Promise::DeferredPromise(Input) < Promise
end

def promise_execute
generic_type = Generic(Output).new.type_var
promise = DeferredPromise(typeof(generic_type)).new
promise = DeferredPromise(typeof(Generic(Output).new.type_var)).new
execute = Proc(Input, Nil).new do |value|
begin
promise.resolve(@callback.call(value))
Expand Down
3 changes: 1 addition & 2 deletions src/promise/implicit_defer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ class Promise::ImplicitDefer(Output)

def execute!
# Replace NoReturn with Nil if the block will always `raise` an error
generic_type = Generic(Output).new
promise = DeferredPromise(typeof(generic_type.type_var)).new
promise = DeferredPromise(typeof(Generic(Output).new.type_var)).new

spawn(same_thread: @same_thread) do
begin
Expand Down
3 changes: 1 addition & 2 deletions src/promise/resolved_promise.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ class Promise::ResolvedPromise(Input) < Promise::DeferredPromise(Input)

def execute!
# Replace NoReturn with Nil if the block will always `raise` an error
generic_type = Generic(Output).new.type_var
promise = DeferredPromise(typeof(generic_type)).new
promise = DeferredPromise(typeof(Generic(Output).new.type_var)).new

spawn(same_thread: true) do
begin
Expand Down

0 comments on commit bebcdcc

Please sign in to comment.