Feature clienttracking - #121
Open
ascottDI wants to merge 6 commits into
Open
Conversation
added 2 commits
August 7, 2026 17:14
DIReview Summary0 critical | 5 warning(s) | 0 suggestion(s)
|
DIReview Summary0 critical | 1 warning(s) | 0 suggestion(s)
|
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.
di.clienttracking
Summary
Adds
di.clienttracking, adi.*module that tracks the client sessions connected to a KDB-Xprocess in an in-memory table: who is connected, from where, when each session opened and closed,
and how many requests and result-bytes each client has been served. It is the modular replacement
for TorQ's
code/handlers/trackclients.q(the.clientsnamespace).The module registers all of its
.z.*hooks through an injecteddi.handlersinstance and neverassigns
.z.*directly, so it composes with any other component hooking the same events.Depends on
di.handlersanddi.log(both merged). Framework tier; no hardusedependencies —all providers are injected, so the module loads and is tested standalone.
Motivation
trackclients.qmaintains aCLIENTSsession table by hand-rolling.z.*handler chaining via.dotz.set. In the modular framework, ownership of the.z.*events belongs todi.handlers, sothis extraction re-expresses the tracking logic on top of the
di.handlersregistration contractwhile preserving the observable behaviour (a queryable table of current and recent client sessions).
Design
Event handling
di.handlersdistinguishes two dispatch models, and this module uses both:.z.po,.z.pc,.z.wo,.z.wcare simple events (return valuediscarded), fanned out to every registrant. The module registers monadic observers that open a
session row on connect and stamp
endpon disconnect..z.pg,.z.ps,.z.wsare phased events whose return value is the queryresult. Counting only observes that result, so it is registered as a
posthandler.Usage-counting activation
di.handlersdoes not accept apostregistration on a phased event until that event has anexecowner.
inittherefore registers the lifecycle observers unconditionally and then callsenableusage[], which registers the usageposthandler on each query event that already has anowner and skips (with a
warn) any that do not.enableusage[]is exported and idempotent so it canbe called again once the process's query owner (e.g. a gateway or
di.permissions) is registered.Dependencies (injected via
init)`log{[c;m]}functions with at leastinfo,warn,error`handlersdi.handlersinstance'sregister/remove/listfunctionsinitvalidates both strictly and signals immediately if either is missing or malformed; there is nofallback.
di.log'slogdictlog(aninfo..fataldict) is a conforming superset of the required log keys and is passed through without adaptation. Optional config keys —maxidle(timespan),retain(timespan),trackusage` (boolean) — are type-checked when present.Although the dependency tree lists
di.clienttracking → di.handlers, handler management is aninjected dependency per the framework contract, not a hard
useimport;di.torqsupplies theshared, initialised
di.handlersinstance toinit.Session table
getclients[]returns the table; each row is one session, and an open session has a nullendp:w`g#int.z.wat open)ipau.z.uat open)a.z.aat open)startpendplastphitsszChanges from the TorQ implementation
w) instead of keyed-on-handleCLIENTSon the handle and nulled the key on close, which collides on handle reuse; an unkeyed table represents concurrent and historical sessions per handle cleanly (current session = row with thatwand nullendp).errscolumn removedposthandler runs only after a successfulexec, so it cannot observe failed queries; the column would always be zero.evalback to each client to self-report host details. It only works for a cooperating q client and is security-sensitive; removed, along with the columns it populated.cleanup[]for a host to schedule viadi.timer, keeping the injected surface tolog+handlers.Public API
init[deps]getclients[]addclient[handle]addw).cleanup[]maxidle, and purge closed rows older thanretain. Also runs automatically on every open/close.enableusage[]execowner.version"0.1.0").getapimeta[]exposes the callable API above for central registration withdi.api; the frameworkplumbing (
init,getapimeta,version) is intentionally excluded.Testing
Run against the real, merged
di.handlersanddi.log— no dependencies are mocked.Unit suite —
test.csv, 41 checks, hermetic (no sockets). Drivesdi.handlers' dispatcher byinvoking the function bound to each
.z.*event, exercising the full registration → dispatch → updatepath. Covers dependency and config validation, the four lifecycle registrations, connect/disconnect
tracking,
addclientand its input validation, cleanup of departed handles, usage-counting activationand the deferral path when no owner exists, teardown on
trackusage:0b, and the API-metadata/versioncontract.
Integration suite —
test_integration.csv, 6 checks. Stands up a child q process, tracks thehandle to it, and exercises the idle-reap path (which requires a genuinely live handle): it verifies
that an idle handle is force-closed and its session stamped closed, and that the child is cleaned up.
Requires a q/kdb-x binary on
QHOMEand skips cleanly if none is available.moduletestloads onlytest.csv, so run the integration suite directly:Incoming-connection acceptance (
.z.pofiring on a real socket) is not integration-tested: a processonly accepts inbound connections at its top-level event loop, which the test harness does not reach.
That binding is the responsibility of
di.handlersand is covered by its own integration suite; herethe
.z.po/.z.pchandlers are exercised through the unit suite.Limitations
posthandler runs in the query'sexecution context; on a multithreaded (negative
\p) port that is not the main thread, so itsupdate to the session table hits kdb's
'noupdaterestriction.di.handlersisolates the handler,so the query still succeeds and the update is skipped with a logged warning. Lifecycle tracking is
unaffected.
execowner; re-runenableusage[]after an owner is registered.ipaformats.z.aas a dotted-decimal address without reverse-hostname resolution.Files