Module: CableRoom::Room::Threading

Extended by:
ActiveSupport::Concern
Included in:
Base
Defined in:
lib/cable_room/room/threading.rb

Instance Method Summary collapse

Instance Method Details

#async(&blk) ⇒ Object

Run the given block off the Room's thread, so slow work doesn't stop the Room from processing anything else.

A Room is otherwise single-threaded: messages and timers run one at a time, which is what makes Room state safe to touch without locks. Work posted here deliberately escapes that queue and runs concurrently with the Room, so it must not reference Room state. Capture everything the block needs before posting it:

token = message_origin.token
async { expensive_lookup(token) }          # good - `token` was captured

async { expensive_lookup(message_origin) } # BAD - races the Room's thread

In particular message is nil inside the block (it is thread-local to the Room's thread), and message_origin and reply refer to whatever the Room is handling now rather than what it was handling when async was called.

self is still the Room, so instance methods resolve normally - which is exactly why the state rule matters. To act on the result, hand it back to the Room's thread:

token = message_origin.token
async do
result = expensive_lookup(token)
on_room_thread { broadcast({ type: 'result', result: result }, client_port: token) }
end

Exceptions are reported through CableRoom.error_handler, the same as any other Room work.

Note that this borrows a thread from the worker pool shared by every Room in the process, so blocking a Room's thread waiting on async work can starve other Rooms. Prefer handing results back with on_room_thread over waiting for them.



37
38
39
40
# File 'lib/cable_room/room/threading.rb', line 37

def async(&blk)
  room = self
  @cable_channel.post_work(async: true) { room.instance_exec(&blk) }
end

#on_room_thread(&blk) ⇒ Object

Queue the block back onto the Room's own thread, where touching Room state is safe again.



43
44
45
46
# File 'lib/cable_room/room/threading.rb', line 43

def on_room_thread(&blk)
  room = self
  @cable_channel.post_work(async: false, silent: true) { room.instance_exec(&blk) }
end