Skip to content

Generic socket custom-command seam, shared archive path/fill logic, and typed-value support#39

Open
ritchiecarroll wants to merge 4 commits into
developmentfrom
updated-event-details-ops
Open

Generic socket custom-command seam, shared archive path/fill logic, and typed-value support#39
ritchiecarroll wants to merge 4 commits into
developmentfrom
updated-event-details-ops

Conversation

@ritchiecarroll

@ritchiecarroll ritchiecarroll commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Three related, low-level capabilities that support openHistorian's day-sharded event-details feature (see Related PRs), all designed to be backward compatible:

  1. A generic socket "custom command" facility (a reusable RPC passthrough).
  2. Shared archive directory/path selection logic plus a configurable fill method (sequential vs. round-robin).
  3. Typed-value BitConvert helpers used by openHistorian's typed HistorianValue.

SnapDB intentionally has no knowledge of "event details" — openHistorian plugs into the generic seam.

1. Generic socket custom-command facility

  • New ServerCommand.RunCustomCommand = 8 and ServerResponse values CustomCommandResult / CustomCommandNotSupported / CustomCommandError.
  • SnapServer.RegisterCustomCommand(name, Func<byte[],byte[]>) / UnregisterCustomCommand / internal TryGetCustomCommand — a thread-safe handler registry.
  • Server dispatch added to SnapStreamingServer.ProcessRootLevelCommands (root level, since handlers are process-scoped). A known command with no registered handler responds CustomCommandNotSupported without dropping the connection.
  • SnapClient.RunCustomCommand(name, request)virtual on the base, overridden by SnapStreamingClient (over the socket; throws if used while connected to a database) and the in-process SnapServer.Client (direct invoke).
  • New CustomCommandNotSupportedException so callers can detect and degrade gracefully.

Backward compatibility

  • The Read/Write wire formats are unchanged.
  • Old client → new server: old clients never send command 8; all existing commands behave identically.
  • New client → old server: the old server hits its default case, returns UnknownCommand, and drops that connection. The client surfaces this as CustomCommandNotSupportedException, so callers can degrade. (Use a dedicated connection for custom commands so a drop never disrupts data reads — openHistorian's client does this.)
  • There is no feature-negotiation handshake, so capability is discovered by attempting the command and catching the exception.

2. Shared archive path/fill logic + configurable fill method

  • New ArchivePathHelper consolidates directory-layout and write-path selection logic that was duplicated across ArchiveInitializer and SimplifiedArchiveInitializer:
    • GetRelativeDirectory(time, method) — dated sub-path (pure).
    • GetPath(root, time, method) — combine + create.
    • GetPathWithEnoughSpace(paths, estimatedSize, desiredRemainingSpace, fillMethod, seed) — disk-space-aware selection.
  • New ArchiveDirectoryFillMethod { Sequential, RoundRobin }, plumbed through ArchiveInitializerSettings.FillMethod, SimplifiedArchiveInitializerSettings.FillMethod, and AdvancedServerDatabaseConfig.FillMethod.
  • The YearThenMonth layout now uses Path.Combine instead of a literal \ separator (cross-platform; identical output on Windows).

Reviewer / tester considerations

  • Round-robin is opt-in. The default is Sequential, which preserves the historical first-fill behavior (fill the first write directory until it lacks space, then advance). The previous code's comment claimed round-robin but actually behaved as sequential first-fill; behavior is unchanged unless FillMethod = RoundRobin is configured.
  • The fill method is selected end-to-end from openHistorian's DirectoryFillMethod connection-string parameter (see openHistorian PR). Multi-drive deployments should verify expected file distribution after choosing a mode.
  • Settings serialization bumped to version 2: ArchiveInitializerSettings/SimplifiedArchiveInitializerSettings now write a fill-method field. A v2-aware build reads old v1 streams (defaulting to Sequential); older builds cannot read v2 streams. Relevant only where these settings are persisted/transferred across mixed versions.

3. Typed-value BitConvert helpers

  • BitConvert.ToDouble(ulong) and BitConvert.ToUInt64(double) added to support openHistorian's typed HistorianValue (64-bit value encodings).

Reviewer / tester considerations (overall)

  • No behavior change unless (a) a custom-command handler is registered (openHistorian registers "GetEventDetails"), or (b) FillMethod = RoundRobin is configured.
  • Publish ordering: openHistorian's non-Development builds consume these via the Gemstone.SnapDB package, so this must be published before those builds pick up the feature.

Testing

The custom-command path (client ⇄ server framing, registry, graceful degradation) is exercised end-to-end by openHistorian's EventDetailsSocketTests, including a live TCP round-trip through SnapStreamingClient/SnapStreamingServer.

Related PRs

This change is part of an event-details feature spanning three repositories:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant