@@ -23,12 +23,17 @@ At this point the SDK only supports the **Temporal Client** capabilities:
2323- gRPC access to Temporal Server
2424- Temporal Cloud is not yet supported due to the lack of TLS support, but it's coming soon
2525
26+ As well as ** Activity Worker** capabilities:
27+
28+ - Definiting activities
29+ - Activity heartbeats/cancellations
30+ - Running activity workers
2631
2732## Quick Start
2833
2934### Installation
3035
31- Add the [ ` temporalio ` gem] ( https://rubygems.org/gems/temporalio ) to your Gemfile:
36+ Add the [ temporalio gem] ( https://rubygems.org/gems/temporalio ) to your Gemfile:
3237
3338``` ruby
3439gem ' temporalio'
@@ -82,12 +87,164 @@ The default data converter supports converting multiple types including:
8287
8388- ` nil `
8489- bytes (` String ` with ` Encoding::ASCII_8BIT ` )
85- - Anything that [ ` JSON.generate ` ] ( https://ruby-doc.org/stdlib-3.0.0/libdoc/json/rdoc/JSON.html#method-i-generate )
90+ - Anything that [ JSON.generate] ( https://ruby-doc.org/stdlib-3.0.0/libdoc/json/rdoc/JSON.html#method-i-generate )
8691 supports
8792
8893This notably doesn't include any ` Date ` , ` Time ` , or ` DateTime ` objects as they may not work across
8994different SDKs. A custom payload converter can be implemented to support these.
9095
96+ ### Workers
97+
98+ Workers host workflows (coming soon) and/or activities. Here's how to run a worker:
99+
100+ ``` ruby
101+ require ' temporal'
102+
103+ # Establish a gRPC connection to the server
104+ connection = Temporal ::Connection .new (' localhost:7233' )
105+
106+ # Initialize a new worker with your activities
107+ worker = Temporal ::Worker .new (connection, ' my-namespace' , ' my-task-queue' , activities: [MyActivity ])
108+
109+ # Occupy the thread by running the worker
110+ worker.run
111+ ```
112+
113+ Some things to note about the above code:
114+
115+ - This creates/uses the same connection that is used for initializing a client
116+ - Workers can have many more options not shown here (e.g. data converters and interceptors)
117+
118+ In order to have more control over running of a worker you can provide a block of code by the end of
119+ which the worker will shut itself down:
120+
121+ ``` ruby
122+ # Initialize worker_1, worker_2 and worker_3 as in the example above
123+
124+ # Run the worker for 5 seconds, then shut down
125+ worker_1.run { sleep 5 }
126+
127+ # Or shut the worker down when a workflow completes (very useful for running specs):
128+ client = Temporal ::Client .new (connection, ' my-namespace' )
129+ handle = client.start_workflow(' MyWorkflow' , ' some input' , id: ' my-id' , task_queue: ' my-task-queue' )
130+ worker_2.run { handle.result }
131+
132+ # Or wait for some external signal to stop the worker
133+ stop_queue = Queue .new
134+ Signal .trap (' USR1' ) { stop_queue.close }
135+ worker_3.run { stop_queue.pop }
136+ ```
137+
138+ You can also shut down a running worker by calling ` Temporal::Worker#shutdown ` from a separate
139+ thread at any time.
140+
141+ #### Running multiple workers
142+
143+ In order to run multiple workers in the same thread you can use the ` Temporal::Worker.run ` method:
144+
145+ ``` ruby
146+ # Initialize workers
147+ worker_1 = Temporal ::Worker .new (connection, ' my-namespace-1' , ' my-task-queue-1' , activities: [MyActivity1 , MyActivity2 ])
148+ worker_2 = Temporal ::Worker .new (connection, ' my-namespace-2' , ' my-task-queue-1' , activities: [MyActivity3 ])
149+ worker_3 = Temporal ::Worker .new (connection, ' my-namespace-1' , ' my-task-queue-2' , activities: [MyActivity4 ])
150+
151+ Temporal ::Worker .run(worker_1, worker_2, worker_3)
152+ ```
153+
154+ Please note that similar to ` Temporal::Worker#run ` , ` Temporal::Worker.run ` accepts a block that
155+ behaves the same way — the workers will be shut down when the block finishes.
156+
157+ #### Worker Shutdown
158+
159+ The ` Temporal::Worker#run ` (as well as ` Temporal::Worker#shutdown ` ) invocation will wait on all
160+ activities to complete, so if a long-running activity does not at least respect cancellation, the
161+ shutdown may never complete.
162+
163+ ### Activities
164+
165+ #### Definition
166+
167+ Activities are defined by subclassing ` Temporal::Activity ` class:
168+
169+ ``` ruby
170+ class SayHelloActivity < Temporal ::Activity
171+ # Optionally specify a custom activity name:
172+ # (The class name `SayHelloActivity` will be used by default)
173+ activity_name ' say-hello'
174+
175+ def execute (name )
176+ return " Hello, #{ name } !"
177+ end
178+ end
179+ ```
180+
181+ Some things to note about activity definitions:
182+
183+ - Long running activities should regularly heartbeat and handle cancellation
184+ - Activities can only have positional arguments. Best practice is to only take a single argument
185+ that is an object/dataclass of fields that can be added to as needed.
186+
187+ #### Activity Context
188+
189+ Activity classes have access to ` Temporal::Activity::Context ` via the ` activity ` method. Which
190+ itself provides access to useful methods, specifically:
191+
192+ - ` info ` - Returns the immutable info of the currently running activity
193+ - ` heartbeat(*details) ` - Record a heartbeat
194+ - ` cancelled? ` - Whether a cancellation has been requested on this activity
195+ - ` shield ` - Prevent cancel exception from being thrown during the provided block of code
196+
197+ ##### Heartbeating and Cancellation
198+
199+ In order for a non-local activity to be notified of cancellation requests, it must call
200+ ` activity.heartbeat ` . It is strongly recommended that all but the fastest executing activities call
201+ this method regularly.
202+
203+ In addition to obtaining cancellation information, heartbeats also support detail data that is
204+ persisted on the server for retrieval during activity retry. If an activity calls
205+ ` activity.heartbeat(123, 456) ` and then fails and is retried, ` activity.info.heartbeat_details ` will
206+ return an array containing ` 123 ` and ` 456 ` on the next run.
207+
208+ A cancellation is implemented using the ` Thread#raise ` method, which will raise a
209+ ` Temporal::Error::CancelledError ` during the execution of an activity. This means that your code
210+ might get interrupted at any point and never complete. In order to protect critical parts of your
211+ activities wrap them in ` activity.shield ` :
212+
213+ ``` ruby
214+ class ActivityWithCriticalLogic < Temporal ::Activity
215+ def execute
216+ activity.shield do
217+ run_business_critical_logic_1
218+ end
219+
220+ run_non_critical_logic
221+
222+ activity.shield do
223+ run_business_critical_logic_2
224+ end
225+ end
226+ end
227+ ```
228+
229+ This will ensure that a cancellation request received while inside the ` activity.shield ` block will
230+ not raise an exception until that block finishes.
231+
232+ In case the entire activity is considered critical, you can mark it as ` shielded! ` and ignore
233+ cancellation requests altogether:
234+
235+ ``` ruby
236+ class CriticalActivity < Temporal ::Activity
237+ shielded!
238+
239+ def execute
240+ ...
241+ end
242+ end
243+ ```
244+
245+ For any long-running activity using this approach it is recommended to periodically check
246+ ` activity.cancelled? ` flag and respond accordingly.
247+
91248
92249## Dev Setup
93250
0 commit comments