Module: Mbeditor::CollaborationDocStore

Defined in:
app/services/mbeditor/collaboration_doc_store.rb

Overview

Thread-safe, in-memory cache of opaque Yjs bytes per active file. It never interprets the bytes (no Ruby CRDT): it only buffers the latest snapshot plus recent deltas so a late-joiner can sync instantly. Modeled on the existing TTL caches (GitInfoService, AvailabilityProbe): Mutex + monotonic clock.

Constant Summary collapse

GRACE_TTL =

Grace window (seconds): a room with no activity for longer than this is evicted by sweep!. Because every op refreshes last_activity, a room that just went empty survives this long, so a quick reopen recovers its buffer.

300
ROOM_CAP =

Hard cap on cached rooms. Exceeding it evicts the least-recently-active room, so process memory stays bounded even if rooms are never swept.

200
SWEEP_INTERVAL =

Idle GC rides on traffic: every write/read attempts a sweep, but the scan runs at most once per this interval so a busy room doesn't pay for it on every op. With no explicit scheduler, memory stays bounded over a long session as long as some activity continues. Kept well under GRACE_TTL so an idle room is reclaimed soon after its grace window elapses.

60
MAX_DELTAS =

Hard cap on buffered deltas per room; a long editing session on one file would otherwise grow it without limit. Over the cap the oldest go, which costs a late joiner an incomplete replay — it then waits for the next snapshot instead of syncing from the buffer.

500

Class Method Summary collapse

Class Method Details

.record_update(path, bytes, now: monotonic) ⇒ Object



36
37
38
39
40
41
42
43
# File 'app/services/mbeditor/collaboration_doc_store.rb', line 36

def record_update(path, bytes, now: monotonic)
  MUTEX.synchronize do
    room = touch(path, now)
    room[:deltas] << bytes
    room[:deltas].shift while room[:deltas].size > MAX_DELTAS
  end
  nil
end

.replace_snapshot(path, bytes, now: monotonic) ⇒ Object



45
46
47
48
49
50
51
52
# File 'app/services/mbeditor/collaboration_doc_store.rb', line 45

def replace_snapshot(path, bytes, now: monotonic)
  MUTEX.synchronize do
    room = touch(path, now)
    room[:snapshot] = bytes
    room[:deltas] = []
  end
  nil
end

.reset!Object



73
74
75
76
77
78
79
# File 'app/services/mbeditor/collaboration_doc_store.rb', line 73

def reset!
  MUTEX.synchronize do
    @rooms = {}
    @last_sweep = nil
  end
  nil
end

.state_for(path, now: monotonic) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'app/services/mbeditor/collaboration_doc_store.rb', line 54

def state_for(path, now: monotonic)
  MUTEX.synchronize do
    room = rooms[path]
    if room
      room[:last_activity] = now
      result = { snapshot: room[:snapshot], deltas: room[:deltas].dup }
    else
      result = { snapshot: nil, deltas: [] }
    end
    maybe_sweep(now)
    result
  end
end

.sweep!(now: monotonic, grace: GRACE_TTL) ⇒ Object



68
69
70
71
# File 'app/services/mbeditor/collaboration_doc_store.rb', line 68

def sweep!(now: monotonic, grace: GRACE_TTL)
  MUTEX.synchronize { evict_idle(now, grace) }
  nil
end