async-actor 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a489ed25c778c1c043b3a312d1e15e051f11c89e794567826937e2e012c3dc9d
4
- data.tar.gz: 1ddd0a6f0a2db32121aa1b10dbc22f37f47914992369546b7e1d2931fbf10af6
3
+ metadata.gz: 7a0faec5a9475a5c7ac0d593c18e2707583043f74329fd07ccf107533877b2af
4
+ data.tar.gz: 5cefcda45e0e6acc25f8418d0340526b8af9849caf7f3e8bc4cc39f35560f353
5
5
  SHA512:
6
- metadata.gz: 301c0d39c26bf5b12fa4169c6f46fd216133548a08b34b25114e5a917678288bda7a6413e996df149403ab7b30b102171861059929c921cd8cfb876eb71e6b87
7
- data.tar.gz: 4006802794c073a8b3c22a418d070639291b851440e5859080faae7400506837654e04cd77ce9ff60a4d47136087eed8b7359271c191172f83b66a94e2e7fb62
6
+ metadata.gz: 55c05936c7a06a08448128d8b98fd2d5258ccdb93303f635add67d3de3f41ec2cc7aa8047f071c04c6a08e831f7245c3fd037521baddaf20e9631e09df139732
7
+ data.tar.gz: 4573e8fff48e9508ae01fc6c65d9d3ea369291057a092db3ca32196694d799e9222b6399c0187e5c28247ebb537a1ed69faec0d9c13eada6645d08006aff36d7
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,148 @@
1
+ # Getting Started
2
+
3
+ This guide explains how to use `async-actor` for asynchronous programming.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your project:
8
+
9
+ ~~~ bash
10
+ $ bundle add async-actor
11
+ ~~~
12
+
13
+ ## Core Concepts
14
+
15
+ `async-actor` provides a simple actor model where each actor runs in its own dedicated thread. The {ruby Async::Actor::Proxy} class creates a proxy interface that wraps any object and executes its methods asynchronously in a separate thread, using an event loop. When you call methods on the proxy, they are queued and processed by the internal actor thread, allowing for concurrent execution while maintaining thread isolation.
16
+
17
+ ### Lifecycle Separation
18
+
19
+ One of the key benefits of actors is **lifecycle separation**. An actor can run for the entire lifetime of your process, independent of your application's async contexts. This means you can have long-running actors that persist even while your frontend usage of Async may start and stop multiple times.
20
+
21
+ For example, you might have a logging actor or database connection pool that runs continuously, while your web server or other components restart their async contexts as needed. The actor maintains its own thread and state, providing a stable foundation for your application's infrastructure. A common use case is in test suites where you need to cache access to background connection pools for performance reasons - you can create an actor that manages database connections or external service clients, allowing you to reuse these expensive resources between tests while each test runs in its own isolated async context.
22
+
23
+ ## Usage
24
+
25
+ Any existing object can be wrapped into an actor:
26
+
27
+ ```ruby
28
+ require "async/actor"
29
+
30
+ require "cgi"
31
+ require "net/http"
32
+ require "json"
33
+
34
+ class Wikipedia
35
+ def summary_url(title)
36
+ URI "https://en.wikipedia.org/api/rest_v1/page/summary/#{CGI.escape title}"
37
+ end
38
+
39
+ def lookup(title)
40
+ JSON.parse(Net::HTTP.get(summary_url(title))).fetch("extract")
41
+ end
42
+ end
43
+
44
+ wikipedia = Async::Actor.new(Wikipedia.new)
45
+
46
+ puts wikipedia.lookup("Ruby_(programming_language)")
47
+ ```
48
+
49
+ The above code looks deceptively simple, however `wikipedia.lookup` actually sends a message to the actor using a message queue. The proxy creates a dedicated thread that runs an async event loop, and the actor processes the message within this thread. This allows the actor to handle multiple messages concurrently within its own thread while keeping it isolated from the main thread. When the result is ready, the actor thread notifies the caller with the result.
50
+
51
+ Be aware that as the actor is running in a separate thread, your code will need to be thread-safe, including arguments that you pass to the actor. Any block you provide will also be executed in the actor's thread.
52
+
53
+ ## Return Value Control
54
+
55
+ The actor proxy provides flexible control over how method calls return values through the `return_value` parameter. This parameter accepts three options:
56
+
57
+ ### `:wait` (Default)
58
+
59
+ By default, method calls block until the result is available and return the actual result:
60
+
61
+ ```ruby
62
+ wikipedia = Async::Actor.new(Wikipedia.new)
63
+
64
+ # This blocks until the lookup completes and returns the extract
65
+ result = wikipedia.lookup("Ruby_(programming_language)")
66
+ puts result # Prints the Wikipedia extract
67
+ ```
68
+
69
+ This is equivalent to:
70
+
71
+ ```ruby
72
+ result = wikipedia.lookup("Ruby_(programming_language)", return_value: :wait)
73
+ ```
74
+
75
+ ### `:promise`
76
+
77
+ Returns an `Async::Promise` immediately, allowing you to wait for the result later:
78
+
79
+ ```ruby
80
+ wikipedia = Async::Actor.new(Wikipedia.new)
81
+
82
+ # Returns immediately with a promise
83
+ promise = wikipedia.lookup("Ruby_(programming_language)", return_value: :promise)
84
+
85
+ # Do other work...
86
+ puts "Looking up information..."
87
+
88
+ # Wait for the result when needed
89
+ result = promise.wait
90
+ puts result
91
+ ```
92
+
93
+ This is useful for fire-and-forget operations or when you want to start multiple operations concurrently:
94
+
95
+ ```ruby
96
+ # Start multiple lookups concurrently
97
+ ruby_promise = wikipedia.lookup("Ruby_(programming_language)", return_value: :promise)
98
+ python_promise = wikipedia.lookup("Python_(programming_language)", return_value: :promise)
99
+ java_promise = wikipedia.lookup("Java_(programming_language)", return_value: :promise)
100
+
101
+ # Wait for all results
102
+ puts "Ruby: #{ruby_promise.wait}"
103
+ puts "Python: #{python_promise.wait}"
104
+ puts "Java: #{java_promise.wait}"
105
+ ```
106
+
107
+ ### `:ignore`
108
+
109
+ Executes the method but discards the result, returning `nil` immediately:
110
+
111
+ ```ruby
112
+ logger = Async::Actor.new(Logger.new)
113
+
114
+ # Fire-and-forget logging - returns nil immediately
115
+ logger.info("Application started", return_value: :ignore)
116
+
117
+ # Continue with other work without waiting
118
+ puts "Continuing execution..."
119
+ ```
120
+
121
+ This is perfect for logging, notifications, or other side-effect operations where you don't need the return value.
122
+
123
+ ## Error Handling
124
+
125
+ Errors from actor method calls are propagated to the caller:
126
+
127
+ ```ruby
128
+ wikipedia = Async::Actor.new(Wikipedia.new)
129
+
130
+ begin
131
+ # This will raise if the network request fails
132
+ result = wikipedia.lookup("NonexistentPage")
133
+ rescue => error
134
+ puts "Lookup failed: #{error.message}"
135
+ end
136
+ ```
137
+
138
+ With promises, errors are raised when you call `wait`:
139
+
140
+ ```ruby
141
+ promise = wikipedia.lookup("NonexistentPage", return_value: :promise)
142
+
143
+ begin
144
+ result = promise.wait
145
+ rescue => error
146
+ puts "Lookup failed: #{error.message}"
147
+ end
148
+ ```
@@ -0,0 +1,13 @@
1
+ # Automatically generated context index for Utopia::Project guides.
2
+ # Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3
+ ---
4
+ description: A multi-threaded actor implementation where each actor has it's own event
5
+ loop.
6
+ metadata:
7
+ documentation_uri: https://socketry.github.io/async-actor/
8
+ funding_uri: https://github.com/sponsors/ioquatix
9
+ source_code_uri: https://github.com/socketry/async-actor.git
10
+ files:
11
+ - path: getting-started.md
12
+ title: Getting Started
13
+ description: This guide explains how to use `async-actor` for asynchronous programming.
@@ -3,25 +3,32 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
- require 'async'
7
-
8
- require_relative 'variable'
6
+ require "async"
9
7
 
10
8
  module Async
11
9
  module Actor
10
+ # Represents an asynchronous proxy that wraps an object and executes its methods in a separate thread.
12
11
  class Proxy < BasicObject
12
+ # Handles cleanup of proxy resources when the proxy is garbage collected.
13
13
  class Finalizer
14
+ # Initialize a new finalizer.
15
+ # @parameter queue [Thread::Queue] The message queue to close.
16
+ # @parameter thread [Thread] The worker thread to join.
14
17
  def initialize(queue, thread)
15
18
  @queue = queue
16
19
  @thread = thread
17
20
  end
18
21
 
22
+ # Clean up proxy resources by closing the queue and joining the thread.
23
+ # @parameter id [Object] The object id (unused but required by ObjectSpace.define_finalizer).
19
24
  def call(id)
20
25
  @queue.close
21
26
  @thread.join
22
27
  end
23
28
  end
24
29
 
30
+ # Initialize a new proxy for the target object.
31
+ # @parameter target [Object] The object to wrap with asynchronous method execution.
25
32
  def initialize(target)
26
33
  @target = target
27
34
 
@@ -35,7 +42,7 @@ module Async
35
42
  # @parameter return_value [Symbol] One of :ignore, :promise or :wait.
36
43
  def method_missing(*arguments, return_value: :wait, **options, &block)
37
44
  unless return_value == :ignore
38
- result = Variable.new
45
+ result = Promise.new
39
46
  end
40
47
 
41
48
  @queue.push([arguments, options, block, result])
@@ -43,7 +50,7 @@ module Async
43
50
  if return_value == :promise
44
51
  return result
45
52
  else
46
- return result&.get
53
+ return result&.wait
47
54
  end
48
55
  end
49
56
 
@@ -55,7 +62,9 @@ module Async
55
62
  while operation = @queue.pop
56
63
  task.async do
57
64
  arguments, options, block, result = operation
58
- Variable.fulfill(result) do
65
+
66
+ # Fulfill the promise with the result of the method call:
67
+ Promise.fulfill(result) do
59
68
  @target.public_send(*arguments, **options, &block)
60
69
  end
61
70
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module Actor
8
- VERSION = "0.1.1"
8
+ VERSION = "0.2.0"
9
9
  end
10
10
  end
data/lib/async/actor.rb CHANGED
@@ -3,11 +3,16 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
- require_relative 'actor/version'
7
- require_relative 'actor/proxy'
6
+ require_relative "actor/version"
7
+ require_relative "actor/proxy"
8
8
 
9
+ # @namespace
9
10
  module Async
11
+ # @namespace
10
12
  module Actor
13
+ # Create a new actor proxy for the given instance.
14
+ # @parameter instance [Object] The target object to wrap in an actor proxy.
15
+ # @returns [Proxy] A new proxy that executes methods asynchronously.
11
16
  def self.new(instance)
12
17
  Proxy.new(instance)
13
18
  end
data/readme.md CHANGED
@@ -16,6 +16,24 @@ Please see the [project documentation](https://socketry.github.io/async-actor/)
16
16
 
17
17
  - [Getting Started](https://socketry.github.io/async-actor/guides/getting-started/index) - This guide explains how to use `async-actor` for asynchronous programming.
18
18
 
19
+ ## Releases
20
+
21
+ Please see the [project releases](https://socketry.github.io/async-actor/releases/index) for all releases.
22
+
23
+ ### v0.2.0
24
+
25
+ ### v0.1.1
26
+
27
+ - Fix dependency on async gem to use `>= 1` instead of `~> 1` for better compatibility.
28
+ - Update guide links in documentation.
29
+
30
+ ### v0.1.0
31
+
32
+ - Initial release of async-actor gem.
33
+ - Core actor model implementation with `Async::Actor` class.
34
+ - Proxy class for safe cross-actor communication.
35
+ - Variable class for thread-safe value storage and updates.
36
+
19
37
  ## Contributing
20
38
 
21
39
  We welcome contributions to this project.
@@ -28,8 +46,8 @@ We welcome contributions to this project.
28
46
 
29
47
  ### Developer Certificate of Origin
30
48
 
31
- This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted.
49
+ In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
32
50
 
33
- ### Contributor Covenant
51
+ ### Community Guidelines
34
52
 
35
- This project is governed by [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
53
+ This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
data/releases.md ADDED
@@ -0,0 +1,15 @@
1
+ # Releases
2
+
3
+ ## v0.2.0
4
+
5
+ ## v0.1.1
6
+
7
+ - Fix dependency on async gem to use `>= 1` instead of `~> 1` for better compatibility.
8
+ - Update guide links in documentation.
9
+
10
+ ## v0.1.0
11
+
12
+ - Initial release of async-actor gem.
13
+ - Core actor model implementation with `Async::Actor` class.
14
+ - Proxy class for safe cross-actor communication.
15
+ - Variable class for thread-safe value storage and updates.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-actor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain:
11
10
  - |
@@ -37,41 +36,41 @@ cert_chain:
37
36
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
37
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
38
  -----END CERTIFICATE-----
40
- date: 2023-12-07 00:00:00.000000000 Z
39
+ date: 1980-01-02 00:00:00.000000000 Z
41
40
  dependencies:
42
41
  - !ruby/object:Gem::Dependency
43
42
  name: async
44
43
  requirement: !ruby/object:Gem::Requirement
45
44
  requirements:
46
- - - ">="
45
+ - - "~>"
47
46
  - !ruby/object:Gem::Version
48
- version: '1'
47
+ version: '2.33'
49
48
  type: :runtime
50
49
  prerelease: false
51
50
  version_requirements: !ruby/object:Gem::Requirement
52
51
  requirements:
53
- - - ">="
52
+ - - "~>"
54
53
  - !ruby/object:Gem::Version
55
- version: '1'
56
- description:
57
- email:
54
+ version: '2.33'
58
55
  executables: []
59
56
  extensions: []
60
57
  extra_rdoc_files: []
61
58
  files:
59
+ - context/getting-started.md
60
+ - context/index.yaml
62
61
  - lib/async/actor.rb
63
62
  - lib/async/actor/proxy.rb
64
- - lib/async/actor/variable.rb
65
63
  - lib/async/actor/version.rb
66
64
  - license.md
67
65
  - readme.md
68
- homepage: https://github.com/socketry/async-actor/
66
+ - releases.md
67
+ homepage: https://github.com/socketry/async-actor
69
68
  licenses:
70
69
  - MIT
71
70
  metadata:
72
71
  documentation_uri: https://socketry.github.io/async-actor/
73
72
  funding_uri: https://github.com/sponsors/ioquatix
74
- post_install_message:
73
+ source_code_uri: https://github.com/socketry/async-actor.git
75
74
  rdoc_options: []
76
75
  require_paths:
77
76
  - lib
@@ -79,15 +78,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
78
  requirements:
80
79
  - - ">="
81
80
  - !ruby/object:Gem::Version
82
- version: '3.0'
81
+ version: '3.2'
83
82
  required_rubygems_version: !ruby/object:Gem::Requirement
84
83
  requirements:
85
84
  - - ">="
86
85
  - !ruby/object:Gem::Version
87
86
  version: '0'
88
87
  requirements: []
89
- rubygems_version: 3.4.10
90
- signing_key:
88
+ rubygems_version: 3.6.9
91
89
  specification_version: 4
92
90
  summary: A multi-threaded actor implementation where each actor has it's own event
93
91
  loop.
metadata.gz.sig CHANGED
Binary file
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
5
-
6
- module Async
7
- module Actor
8
- class Variable
9
- def self.fulfill(variable)
10
- variable.set(yield)
11
- variable = nil
12
- rescue => error
13
- variable&.fail(error)
14
- variable = nil
15
- ensure
16
- # throw, etc:
17
- variable&.fail(RuntimeError.new("Invalid flow control!"))
18
- end
19
-
20
- def initialize
21
- @set = nil
22
- @value = nil
23
-
24
- @guard = Thread::Mutex.new
25
- @condition = Thread::ConditionVariable.new
26
- end
27
-
28
- def set(value)
29
- @guard.synchronize do
30
- raise "Variable already set!" unless @set.nil?
31
-
32
- @set = true
33
- @value = value
34
- @condition.broadcast
35
- end
36
- end
37
-
38
- def fail(error)
39
- @guard.synchronize do
40
- raise "Variable already set!" unless @set.nil?
41
-
42
- @set = false
43
- @error = error
44
- @condition.broadcast
45
- end
46
- end
47
-
48
- def get
49
- @guard.synchronize do
50
- while @set.nil?
51
- @condition.wait(@guard)
52
- end
53
-
54
- if @set
55
- return @value
56
- else
57
- raise @error
58
- end
59
- end
60
- end
61
- end
62
- end
63
- end