Repository navigation
Ruby: generate default client ID per process - #1498
Merged
Merged
Conversation
bgentry
force-pushed
the
bg/ruby-per-process-client-id
branch
from
October 11, 2026 22:49
97bde35 to
8faf2f2
Compare
`River::Config` builds its default client ID (`host-pid-random`) when the config is constructed. Preforking servers such as Puma, Unicorn, or Pitchfork with application preloading construct the config before they fork, so every worker process inherits the same ID. Anything keyed by client ID then conflates sibling processes: `attempted_by`, leader election, and producer bookkeeping such as River Pro's dead-producer rescue, where a live sibling can vouch for a dead sibling's jobs. Go clients always get a unique ID per process. Generate the default ID lazily, on first use in each process. `Config#id` compares the current `Process.pid` against the PID that generated the cached ID and regenerates it in a forked child. A PID check needs no global `Process._fork` hook, covers every fork path, and matches the per-process client handling already used by the Rails integration. The cached PID and ID are swapped in together as one frozen value behind a mutex, so every reader in a process (`Config#id`, `Client#id`, job claims, leadership, and plugins such as River Pro) sees the same stable ID. Host names are truncated so the generated ID always fits the 127-character limit. An explicit `id:` is used unchanged in every process, preserving the Go contract that callers who set IDs keep them unique. Copies made with `Config#with` share the original's ID, including its per-process default, unless the copy overrides `id:`. New specs fork child processes that build clients from a shared config and verify that each child gets a distinct ID tied to its PID, that the parent's ID is unchanged, that explicit IDs survive forks, that the ID is stable across threads, and that claims, leadership, and accessors all agree on the generated ID.
bgentry
force-pushed
the
bg/ruby-per-process-client-id
branch
from
October 12, 2026 20:55
8faf2f2 to
18efcc8
Compare
bgentry
marked this pull request as ready for review
October 12, 2026 20:55
bgentry
enabled auto-merge (squash)
October 12, 2026 20:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
River::Configcurrently builds its default client ID (host-pid-random) when the config is constructed. Preforking servers like Puma, Unicorn, and Pitchfork with application preloading construct the config before forking, so every worker process ends up sharing one client ID. Anything keyed by client ID then conflates sibling processes:attempted_by, leader election, and producer bookkeeping such as River Pro's dead-producer rescue, where a live sibling can vouch for a dead sibling's jobs. Go clients always get a unique ID per process.This generates the default ID lazily on first use in each process.
Config#idcompares the currentProcess.pidagainst the PID that generated the cached ID and regenerates it in a forked child. A PID check needs no globalProcess._forkhook, covers every fork path, and matches the per-process handling the Rails integration already uses. Every reader in a process (Config#id,Client#id, job claims, leadership, plugins) sees the same stable ID, and host names are truncated so a generated ID always fits the 127-character limit.An explicit
id:is used unchanged in every process, keeping Go's contract that callers who set IDs keep them unique. Copies made withConfig#withshare the original's ID unless they overrideid:.New specs fork child processes that build clients from a shared config and check that each child gets a distinct ID tied to its PID, that explicit IDs survive forks, that the ID is stable across threads, and that claims, leadership, and accessors agree on it.