Class: LittleGhost::SessionStores::Filesystem
- Inherits:
-
LittleGhost::SessionStore
- Object
- LittleGhost::SessionStore
- LittleGhost::SessionStores::Filesystem
- Defined in:
- lib/little_ghost/session_stores/filesystem.rb
Overview
Filesystem preserves LittleGhost sessions across process restarts in an application-controlled directory. Use it for durable local development, a single-host service, or processes that share a suitable filesystem.
store = LittleGhost::SessionStores::Filesystem.new(
root: "/var/lib/customer_support/sessions"
)
Configure the resulting store through Configuration#session_store, or pass it directly when opening a Session. Calls for the same session wait for one writer, including when separate Ruby processes share the root.
Safety note: The root contains readable session data and is not
encrypted. Its complete path must be application-controlled: anyone able to read it can read session data, and anyone able to replace it can alter sessions.
Session data is stored as ordinary JSON with canonical String keys. A value that cannot be represented that way raises ProtocolError without replacing the previous snapshot. Shared roots require filesystem support for file locking and atomic rename. Waiting for another process does not pause other scheduled fibers. In a scheduled fiber, file transactions use LittleGhost's shared thread pool. Set Configuration#blocking_pool_capacity during process startup if measurements show calls waiting for its two default workers. Store calls do not accept cancellation or deadlines, so a cross-process lock wait continues until the other process releases it.
Constant Summary collapse
- FORMAT_VERSION =
:nodoc:
1- LOCK_RETRY_INTERVAL =
:nodoc:
0.01
Instance Method Summary collapse
-
#append(id, messages:, state:, metadata:, expected_count:, actor_id: nil) ⇒ Object
Atomically appends sanitized
messagesand returns the updated snapshot. -
#initialize(root:) ⇒ Filesystem
constructor
Creates a store rooted at
rootand creates the directory when needed. -
#load(id, actor_id: nil) ⇒ Object
Returns the stored snapshot for
id, ornilbefore the first write. -
#replace(id, messages:, state:, metadata:, actor_id: nil) ⇒ Object
Atomically replaces the complete persisted snapshot and returns it.
Methods inherited from LittleGhost::SessionStore
#project_conversation, #synchronize, #with_operation_context
Constructor Details
#initialize(root:) ⇒ Filesystem
Creates a store rooted at root and creates the directory when needed.
root must be a private, non-symlinked directory. The application owns
the complete path and must not let an untrusted request choose it.
Raises ArgumentError when the root does not meet those requirements.
48 49 50 51 52 53 54 55 |
# File 'lib/little_ghost/session_stores/filesystem.rb', line 48 def initialize(root:) super() @root = File.(String(root)) raise ArgumentError, "root must not be empty" if root.to_s.empty? FileUtils.mkdir_p(@root, mode: 0o700) validate_root! end |
Instance Method Details
#append(id, messages:, state:, metadata:, expected_count:, actor_id: nil) ⇒ Object
Atomically appends sanitized messages and returns the updated snapshot.
expected_count must match the stored history length. state and
metadata must contain values this store can represent as JSON. Raises
ProtocolError when another writer changed the session or the snapshot
cannot be read or written. Raises Error when actor_id does not match
the session.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/little_ghost/session_stores/filesystem.rb', line 77 def append(id, messages:, state:, metadata:, expected_count:, actor_id: nil) = () state = canonical_map(state) = canonical_map() with_lock(id) do current = read_snapshot(id) validate_actor!(current, actor_id) if current current ||= empty_snapshot(actor_id) unless current.fetch(:messages).length == expected_count raise ProtocolError, "Session changed while it was being updated" end snapshot = { actor_digest: current.fetch(:actor_digest), messages: [*current.fetch(:messages), *].freeze, state:, metadata: }.freeze write_snapshot(id, snapshot) public_snapshot(snapshot) end end |
#load(id, actor_id: nil) ⇒ Object
Returns the stored snapshot for id, or nil before the first write.
actor_id must match the actor that created an existing session.
Raises Error for an actor mismatch and ProtocolError for an invalid or
unsafe persisted snapshot.
62 63 64 65 66 67 68 |
# File 'lib/little_ghost/session_stores/filesystem.rb', line 62 def load(id, actor_id: nil) with_lock(id) do snapshot = read_snapshot(id) validate_actor!(snapshot, actor_id) if snapshot snapshot end end |
#replace(id, messages:, state:, metadata:, actor_id: nil) ⇒ Object
Atomically replaces the complete persisted snapshot and returns it.
state, metadata, and actor_id follow the same requirements as
#append.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/little_ghost/session_stores/filesystem.rb', line 104 def replace(id, messages:, state:, metadata:, actor_id: nil) = () state = canonical_map(state) = canonical_map() with_lock(id) do current = read_snapshot(id) validate_actor!(current, actor_id) if current snapshot = { actor_digest: current ? current.fetch(:actor_digest) : actor_digest(actor_id), messages: .freeze, state:, metadata: }.freeze write_snapshot(id, snapshot) public_snapshot(snapshot) end end |