From f690d10eade6e6b32f0dc3ce1b9680497bda4b8c Mon Sep 17 00:00:00 2001 From: juancwu Date: Wed, 29 Apr 2026 12:37:14 +0000 Subject: [PATCH] add README --- README.md | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a09760..68cdf3e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,129 @@ # cubby -A tiny in-memory key-value cache shared between processes via a Unix socket. \ No newline at end of file +A tiny in-memory key-value cache shared between processes via a Unix socket. + +Stash a value, fetch it from another process. That's it. + +## Run the server + +```bash +go run ./cmd/cubby # default: /tmp/cubby.sock, owner-only (0600) +go run ./cmd/cubby -socket /tmp/c.sock # custom path +go run ./cmd/cubby -group cache # share with members of "cache" group (0660) +go run ./cmd/cubby -group 1001 # numeric GID also works +``` + +## Use it from Go + +```go +import "cubby" + +c, err := cubby.Dial("/tmp/cubby.sock") +if err != nil { ... } +defer c.Close() + +c.Set("hello", "world", 0) // no TTL +c.Set("session", "abc", 5*time.Second) // expires in 5s + +v, ok, err := c.Get("hello") // ok=false means missing (not an error) + +existed, _ := c.Del("hello") +keys, _ := c.Keys() +``` + +A `Client` is a single connection and isn't safe for concurrent use. For +goroutines, use a `Pool`: + +```go +pool := cubby.NewPool("/tmp/cubby.sock", 8) // up to 8 idle conns +defer pool.Close() + +err := pool.Do(func(c *cubby.Client) error { + return c.Set("k", "v", 0) +}) +``` + +See `examples/basic` for a runnable example. + +## Sharing with a group + +By default the socket is `chmod 0600` and chowned to the user running cubby — +only that user can connect. With `-group `, the socket is created `0660` +and chowned to that group, so any user in the group can connect. Everyone +else is still locked out. + +To set this up: + +```bash +sudo groupadd cache # create the group (one-time) +sudo usermod -aG cache alice # add each user who should connect +sudo usermod -aG cache bob +# users need to log out and back in for new group membership to take effect +go run server.go -group cache +``` + +## Talk to it + +The protocol is line-based, so you can poke at it with `nc -U`: + +``` +$ nc -U /tmp/cubby.sock +PING ++PONG +SET hello world ++OK +GET hello +$"world" +SET token abc 5 ++OK +DEL hello +#1 +KEYS +#1 +$"token" +QUIT ++BYE +``` + +## Protocol + +**Commands** (one per line, whitespace-separated, values can be Go-quoted to +include spaces or newlines): + +| Command | Meaning | +| --- | --- | +| `SET [ttl_seconds]` | Stash a value, optional TTL | +| `GET ` | Fetch a value | +| `DEL ` | Remove a key | +| `KEYS` | List all live keys | +| `PING` | Health check | +| `QUIT` | Close connection | + +**Replies** (single sigil, terminated by `\n`): + +| Sigil | Meaning | +| --- | --- | +| `+text` | OK / simple string | +| `-text` | Error | +| `$"value"` | Go-quoted string value | +| `_` | Nil (key not found) | +| `#n` | Count (integer) | + +`KEYS` replies with `#N` followed by N `$"..."` lines. + +## Design notes + +- **Storage** is a single `map[string]entry` behind a `sync.RWMutex`. Plenty + for a process-local cache. Shard by key hash if you ever need more. +- **Expiry** is checked lazily on read and swept once per second. +- **Quoting** uses Go's `strconv.Quote`/`Unquote`, so values can contain + arbitrary bytes including spaces, tabs, and newlines. +- **One goroutine per connection** — idiomatic Go, no event loop needed. +- **Socket permissions** are set via `umask` before `Listen`, so there's no + race where the socket exists with looser bits. +- **Clean shutdown** on `SIGINT`/`SIGTERM` removes the socket file. + +## What it doesn't do + +No persistence, pub/sub, transactions, or data types beyond strings. It's a +shared in-memory cache. If you need any of that, reach for something bigger.