very fast at protocol indexer with flexible filtering, xrpc queries, cursor-backed event stream, and more, built on fjall
rust fjall at-protocol atproto indexer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121// Package hydrant runs hydrant inside a go program through its C api.//// hydrant keeps serving its usual http api, so after Start you talk to it over the bind you// gave it, ideally a unix socket. link against libhydrant_ffi.a by putting its directory in// CGO_LDFLAGS, eg. CGO_LDFLAGS=-L/path/to/target/release.package hydrant
/*#cgo CFLAGS: -I${SRCDIR}/../include#cgo LDFLAGS: -lhydrant_ffi#cgo darwin LDFLAGS: -framework SystemConfiguration -framework Security -framework CoreFoundation -liconv -lm// no -lgcc_s because musl has no shared one, and gcc already pulls in an unwinder// either way (libgcc_s when dynamic, libgcc_eh when static)#cgo linux LDFLAGS: -lutil -lrt -lpthread -lm -ldl#include <stdlib.h>#include "hydrant.h"*/import "C"
import ( "encoding/json" "errors" "fmt" "os/signal" "syscall" "time" "unsafe")
// ErrShutdown is what Err reports after Shutdown stopped hydrant.var ErrShutdown = errors.New("hydrant was shut down")
// Hydrant is a hydrant running in this process, until Shutdown.type Hydrant struct { handle *C.hydrant_t done chan struct{} err error}
// Start runs hydrant with the settings its binary reads from the environment, like// HYDRANT_DATABASE_PATH and RUST_LOG, given as a map instead. the process environment is// ignored. HYDRANT_API_BIND is required, and "none" runs without an api. hydrant binds it in// the background, so the api may need a moment before it answers. it also ignores SIGPIPE// for the whole process, so writing to a closed pipe returns EPIPE instead of exiting.func Start(settings map[string]string) (*Hydrant, error) { raw, err := json.Marshal(settings) if err != nil { return nil, fmt.Errorf("encode hydrant settings: %w", err) } cSettings := C.CString(string(raw)) defer C.free(unsafe.Pointer(cSettings))
// hydrant writes to sockets whose peer may be gone, and on linux that raises // SIGPIPE on a rust thread, which go doesn't handle and so the default kills // the whole process. rust binaries ignore it at startup, and this does the same signal.Ignore(syscall.SIGPIPE)
var cErr *C.char handle := C.hydrant_start(cSettings, &cErr) if handle == nil { return nil, takeErr(cErr) }
h := &Hydrant{handle: handle, done: make(chan struct{})} go func() { var cErr *C.char if C.hydrant_wait(handle, &cErr) != 0 { h.err = takeErr(cErr) } else { h.err = ErrShutdown } close(h.done) }() return h, nil}
// Done is closed once hydrant stops, because it failed or Shutdown stopped it. a failed// hydrant still holds its database until Shutdown.func (h *Hydrant) Done() <-chan struct{} { return h.done}
// Shutdown stops hydrant and waits up to timeout for its database to close, so its memory// is given back and Start can open the same database again. whatever hydrant was in the// middle of is cut off like a crash, which it recovers from on the next start. call it// once, from one goroutine, also after a failure. it errors if the database is still open// after timeout, and the Hydrant can't be used again either way.func (h *Hydrant) Shutdown(timeout time.Duration) error { if h.handle == nil { return errors.New("hydrant was already shut down") } var cErr *C.char var err error if C.hydrant_shutdown(h.handle, C.uint64_t(timeout.Milliseconds()), &cErr) != 0 { err = takeErr(cErr) } // the wait goroutine still uses the handle until it sees the stop <-h.done C.hydrant_free(h.handle) h.handle = nil return err}
// Err says why hydrant stopped. it's nil while hydrant is running.func (h *Hydrant) Err() error { select { case <-h.done: return h.err default: return nil }}
func takeErr(cErr *C.char) error { if cErr == nil { return errors.New("hydrant failed without saying why") } defer C.hydrant_free_string(cErr) return errors.New(C.GoString(cErr))}