jj-commit-cloud-poc / The server

The server

jj-cc-server is one tonic process serving a health service, BackendService and OpStoreService. All three share a single in-memory store. Each RPC checks that the repository is registered, computes IDs where needed, and reads or writes the store.

On this page
  1. Command-line arguments
  2. Startup
  3. Repository registration
  4. BackendService, RPC by RPC
  5. OpStoreService, RPC by RPC
  6. Status codes
  7. What the server doesn't do

In short: four flags (--host, --port, --store-type, --sqlite-path), and only the memory store runs. Writes compute their object's ID server-side, except WriteCommit, which keeps a commit ID the client supplies. Reads return the stored object, a synthesized root, or NotFound. Symlink RPCs return Unimplemented. Registration hands out a random UUID per repository and keeps it only in memory, so a restart forgets every repository.

The crate is jj-commit-cloud-server, and its binary is jj-cc-server (server/Cargo.toml). The wire messages are on protobufs, the hashing on object-ids, and the clients of these RPCs on client-backend and client-op-store.

Command-line arguments

FlagTypeDefaultEffect
--hoststring127.0.0.1Interface to bind.
-p, --portu168080Port. 0 picks a free one, and the log line shows which.
--store-typememory | sqlitememorysqlite prints "SQLite storage backend is not yet implemented" and exits with status 1.
--sqlite-pathpathnoneParsed but never read. Its help text names ~/.jj-cc-server/commit_cloud.db as the default.

StoreType and Args · store selection

Startup

  1. Logging goes to stdout through tracing_subscriber, filtered by RUST_LOG plus a jj_cc_server=info directive (L62–68).
  2. The arguments are parsed, and a SQLite request exits (L70–77).
  3. The health reporter marks the overall service "" and OpStoreService as Serving. BackendService is never given a status, so a health check naming it gets an error rather than Serving (L81–87).
  4. A TcpListener binds host:port and logs the actual address (L89–91).
  5. CommitCloudServerImpl::default() creates one Arc<dyn Store> around a fresh MemoryStore. Both services get a clone of that Arc (L48–58, L94–96).
  6. Server::builder() adds the three services and serves until Ctrl-C, then shuts down gracefully (L98–115).

The server sets no TLS, authentication, message-size limits or timeouts, so tonic's defaults apply.

How a request moves through jj-cc-server A gRPC request reaches the tonic server, which routes it to the health service, the BackendService implementation or the OpStoreService implementation. Both service implementations call ensure_repo_registered_error, compute IDs with hash_utils on writes, and read or write the shared Store trait object, whose only implementation is MemoryStore. tonic Server health service CommitCloudBackendService CommitCloudOpStoreService ensure_repo_registered hash_utils Store MemoryStore

Repository registration

RegisterRepository ignores its request, including the optional name, generates a random version-4 UUID, adds it to the store's set of registered repositories (register_repo), and returns it as repo_id. The client calls it once, while the backend's init runs, and writes the ID into config.toml (cc_backend.rs; see client-backend). Each jj cc init therefore makes a new, empty repository. There is no RPC to list or delete repositories, and no way for two clients to share a repo ID other than copying the config.

Every other RPC calls ensure_repo_registered_error, which returns NotFound with "repository should have been registered before …" when the ID is unknown. Since the registered set lives in memory, every repository is unknown after a restart, and existing clients get NotFound on every call.

BackendService, RPC by RPC

RPCValidationWhat it doesErrorsSource
RegisterRepositoryNoneSee above.NoneL25
ReadCommitRepo registeredReturns the stored commit. The root commit isn't synthesized; the client answers it locally.NotFoundL35
WriteCommitRepo registered, then commit presentIf commit_id is empty, computes it with compute_git_commit_hash. Otherwise keeps the client's ID unchecked. Stores the commit under that ID and returns it.NotFound, InvalidArgumentL55
ReadTreeRepo registeredThe empty tree ID (EMPTY_TREE_ID_BYTES) returns no entries without a lookup. Otherwise returns the stored entries.NotFoundL80
WriteTreeRepo registeredComputes the ID with compute_git_tree_hash and stores the entries.NotFoundL108
ReadFileRepo registeredServer-streaming in the proto, but sends the whole file as one chunk, from a spawned task over a channel of capacity 1.NotFoundL124
WriteFileRepo registeredUnary. Computes the ID with compute_git_blob_hash and stores the bytes. A TODO says large files (over 4 MB) need a streaming upload.NotFoundL151
ReadSymlink, WriteSymlinkNoneNot implemented.UnimplementedL169

Writes are idempotent by construction: writing the same object twice computes the same ID and overwrites the entry with identical data. A client-supplied commit ID is the exception. Nothing stops two different commits from being stored under one ID, and the later write wins (see object-ids). A file-level TODO asks for logging of file and operation IDs (L1).

OpStoreService, RPC by RPC

RPCValidationWhat it doesErrorsSource
ReadOperationRepo registeredReturns the stored operation. Failing that, the root ID gets a synthesized root operation.NotFoundL25
WriteOperationoperation present, then repo registeredComputes the ID with hash_operation and stores the operation.InvalidArgument, NotFoundL78
ReadViewRepo registeredReturns the stored view. Failing that, the root ID gets a view whose only head is the root commit.NotFoundL103
WriteViewview present, then repo registeredComputes the ID with hash_view and stores the view.InvalidArgument, NotFoundL143
GetOpHeadsRepo registeredReturns the stored heads, or [root operation] if none were ever written.NotFoundL165
UpdateOpHeadsRepo registeredPasses old_op_head_ids and new_op_head_id to the store, and returns the resulting heads.NotFoundL186

The root operation and view are written out on client-op-store, along with what the store's update rule does to concurrent publishes. The store itself (the Store trait and MemoryStore) is covered on the storage page. Every op-store RPC logs one info line with the repo ID, and reads also log the hex ID.

Status codes

CodeWhen
NotFoundUnregistered repo ID (error_util.rs), or an object that isn't stored and isn't a synthesized root.
InvalidArgumentWriteCommit, WriteOperation or WriteView without its object.
UnimplementedReadSymlink, WriteSymlink.

Nothing checks ID lengths, the shape of a commit or tree, or whether referenced objects exist. A commit can name parents or a tree the server has never seen, and an operation can name a view that isn't stored.

What the server doesn't do

  • Only the in-memory store exists. Repositories, objects and op heads are lost on restart. --store-type sqlite exits, and --sqlite-path is unused.
  • No authentication, TLS, quotas, or per-repository access control. Anyone with a repo ID can read and write it.
  • No validation of IDs, object contents or references, and client-supplied commit IDs are trusted.
  • Symlinks are unimplemented, and files travel as single messages either way.
  • No RPC lists, deletes or garbage-collects anything.
  • BackendService isn't registered with the health service.