[#398] Resolve "signalc: relay messages to/from signalboost over unix socket"
Closes #398 (closed)
Context
- in prior MRs we have given signalc the ability to send and receive messages over signal, storing user data in postgresql across sessions
- in this MR, we give it the ability to receive messages over a UNIX socket from signalboost and send responses back
- currently, signalc understands only the bare minimum of commands necessary to work with signalboost:
register
verify
subscribe
send
- which is just to say, it implements all the features necessary to be load tested against signald!
🎉
Changes
Socket Layer
- new arch elements:
SocketServer
,SocketReceiver
,SocketSender
-
SocketServer
- listens to connections
- on connections: stores a hash map of known connections and assigns receivers and senders to each
- can shutdown a subset or all connections gracefully
-
SocketReceiver
- acts as a router for incoming socket requests (json deserialized using the new
SocketRequest
class) - handles all commands decleared in signald interface:
- actuallyimplements register, verity, subscribe, send
- performs NOOPs for set_expiration, trust, unsubscribe, and version
- after performing appropriate business logic, produces a response (json serialized using new
SocketResponse
class) and routes it back to socket viw theSocketSender
- has slightly more resilient handling of a disrupted subscribe request that signald (attempts to restart disrupted connections and reports them back to signalboost)
- acts as a router for incoming socket requests (json deserialized using the new
-
SocketSender
:- uses actors to create a pool of PrintWriters, spread traffic randomly over each of them, and ensure one-at-a-timne writing of them
- this means: responses will be loadbalanced over all open connections, rather than always going to the connection that opened a subscription (as in signald)
Application/Config/Main
- add run and stop methods
- before calling
run
, all app components are initialized - after calling
run
, the appropriate configs are read in and components are initialized to either their real or mocked values - trick to make this work:
- provie default mocks for every app component (so they don't need to be declared explicitly every time in tests)
- provide Config helpers to mock an arbitrary subset of classes in app for any given test (uses default mocks declared in application class)
- now
Main
is very thin: it does nothing except instantiateApplication
with correct configs, provide it a coroutine scope and callrun
Signal Layer
These classes stayed more or less the same except we memoize signal service receivers and senders and in the receiver class we:
- exaustively pattern match on all possible envelope types
- handle
PREKEY_BUNDLE
envelopes likeCIPHERTEXT
because that is what the first given ciphertext from any new correspondent is labeled as by signal - are a bit smarter about how we are using coroutiens
New Conventions / Code Health
- extact
CacheUtil#getMemoized
to abstract the 4-5 instances in which we want to create memoized instances of Signal Service objects - prefer explicit
async.await()
calls towithContext
for wrapping blocking code - add custom
SignalcError
s - provide
EnvelopeType
andSignalcAddress
classes for kotlin-ifying corresponding SignalService classes
Tests
- provide new
TestSocketServer
andTestScoketClient
classes for reusably and cleanly sending socket messages - use this to improve on semantics of socket tests by receiving connections off of channels (rather than waiting an arbitrary amount of time and grabbing connections out of maps -- which is brittle and breaks encapsulation!)
- implement our first "big bracket" unit test,
SocketServerBigTest
, which tests round-trip message handling from: client to socket server to socket reader to signal sender, to signal receiver, to socket sender and back to client (yay!) - provide tons of new test dat generators and custom matchers
Tooling
- a whole new logging paradigm using sl4j (and some mods to the signalboost logger to be consistent with it)
- configs defined in
main.resources.logback.xml
- reads log level from
LOG_LEVEL
env var - provides custom template for logs -- including thread/coroutine info for DEBUG and ERROR log levels
- configs defined in
- lots of make scripts to run everything
- docs and tooling to attach a remote debugger to signalc while it runs inside a docker container
- docker tooling to inject a different db for signalboost in the sc stack
- env var flags to disable unimplemented signalboost jobs in the sc stack, or set socket pools/shards to lower sizes