Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,44 @@ jobs:

# shellcheck is preinstalled on the ubuntu-latest runner image.
- name: shellcheck
run: shellcheck scripts/install.sh scripts/uninstall.sh scripts/build-local-binary.sh
run: shellcheck scripts/install.sh scripts/uninstall.sh scripts/build-local-binary.sh apps/cli/test/native-checkpoint-smoke.sh

# Syntax-check the POSIX installers against the actual /bin/sh they claim.
- name: sh -n
run: |
sh -n scripts/install.sh
sh -n scripts/uninstall.sh

local-checkpoint-native:
name: Local checkpoint native
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac # v2.4.4

- run: bun install --frozen-lockfile

- name: Build native local bundle
run: scripts/build-local-binary.sh

- name: Run repeated checkpoint restore and fresh-process reopen smoke
env:
KEEP_ROOT: "1"
TMPDIR: ${{ runner.temp }}/maple-native-checkpoint
run: |
mkdir -p "$TMPDIR"
apps/cli/test/native-checkpoint-smoke.sh "$GITHUB_WORKSPACE/dist"

- name: Upload checkpoint failure evidence
if: ${{ failure() }}
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
with:
name: native-checkpoint-failure-${{ github.run_id }}
path: ${{ runner.temp }}/maple-native-checkpoint
if-no-files-found: ignore

service-map-perf:
name: Service Map Perf
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"type": "module",
"scripts": {
"start": "bun run src/bin.ts",
"test": "vitest run",
"test": "bun test test/*.test.ts src/server/otlp/encode.test.ts",
"typecheck": "tsc --noEmit"
},
"dependencies": {
Expand Down
41 changes: 29 additions & 12 deletions apps/cli/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Mode } from "./core/mode"
import { TelemetryLayer } from "./core/telemetry"
import { maybeNotifyUpdate } from "./core/update"
import { WarehouseExecutorFromMode } from "./core/warehouse"
import { CHECKPOINT_REOPEN_PROBE_ENV, validateCheckpointDataDir } from "./server/checkpoints"
import { MAPLE_VERSION } from "./version"

// WarehouseExecutorFromMode needs Mode (which needs MapleConfig). provideMerge
Expand All @@ -26,15 +27,31 @@ const MainLayer = WarehouseExecutorFromMode.pipe(
// (network is hit at most once per 24h), so the latency cost is negligible.
//
// `cli.argv` records the sub-command + flags so one root span per invocation
// ties a command to the warehouse queries it runs. TelemetryLayer is provided
// OUTERMOST (after MainLayer), not merged into it: the OTLP tracer's batch
// exporter flushes when its layer scope closes, and only the outermost provide's
// scope is the runtime's main scope that `BunRuntime.runMain` closes on exit.
// Merging it into MainLayer leaves spans unflushed for short-lived commands.
maybeNotifyUpdate.pipe(
Effect.flatMap(() => Command.run(cli, { version: MAPLE_VERSION })),
Effect.withSpan("maple", { attributes: { "cli.argv": process.argv.slice(2).join(" ") } }),
Effect.provide(MainLayer),
Effect.provide(TelemetryLayer),
BunRuntime.runMain,
)
// ties a command to the warehouse queries it runs. A single merged layer keeps
// both layer lifecycles in the runtime's main scope, so telemetry flushes when
// `BunRuntime.runMain` closes that scope for short-lived commands.
const RuntimeLayer = Layer.merge(MainLayer, TelemetryLayer)

const checkpointProbeDataDir = process.env[CHECKPOINT_REOPEN_PROBE_ENV]

if (checkpointProbeDataDir !== undefined) {
// Private re-exec path used by checkpoint restore. It intentionally bypasses
// CLI dispatch, update checks, telemetry, and schema bootstrap: success means
// this new process loaded the persisted restored representation and queried
// its core tables before closing chDB cleanly.
try {
process.stdout.write(`${JSON.stringify(validateCheckpointDataDir(checkpointProbeDataDir))}\n`)
} catch (error) {
process.stderr.write(
`checkpoint reopen probe failed: ${error instanceof Error ? error.message : String(error)}\n`,
)
process.exitCode = 1
}
} else {
maybeNotifyUpdate.pipe(
Effect.flatMap(() => Command.run(cli, { version: MAPLE_VERSION })),
Effect.withSpan("maple", { attributes: { "cli.argv": process.argv.slice(2).join(" ") } }),
Effect.provide(RuntimeLayer),
BunRuntime.runMain,
)
}
4 changes: 3 additions & 1 deletion apps/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { metrics, query } from "./commands/data"
import { timeseries, breakdown, compare } from "./commands/analytics"
import { login, logout, whoami } from "./commands/auth"
import { use } from "./commands/config"
import { start, stop, reset } from "./commands/server"
import { start, stop, reset, checkpoint, restore } from "./commands/server"
import { update } from "./commands/update"

// One CLI, two backends. Every query command bottoms out at the shared
Expand Down Expand Up @@ -46,6 +46,8 @@ export const cli = Command.make("maple").pipe(
start,
stop,
reset,
checkpoint,
restore,
// Self-update
update,
// Services
Expand Down
28 changes: 28 additions & 0 deletions apps/cli/src/commands/server-args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export type DirtyStorePolicy = "wipe" | "fail" | "restore-checkpoint"

export interface DetachedChildArgs {
readonly entry: string | undefined
readonly port: number
readonly dataDir: string
readonly offline: boolean
readonly chdbConfigFile: string | undefined
readonly onDirtyStore: DirtyStorePolicy
}

/** Build the foreground child argv without forwarding compiled-Bun virtual
* entrypoints or the background flag that caused the re-exec. */
export const buildDetachedChildArgs = (options: DetachedChildArgs): string[] => {
const runtimeArgs = options.entry && !options.entry.startsWith("/$bunfs") ? [options.entry] : []
return [
...runtimeArgs,
"start",
"--port",
String(options.port),
"--data-dir",
options.dataDir,
"--on-dirty-store",
options.onDirtyStore,
...(options.chdbConfigFile ? ["--chdb-config-file", options.chdbConfigFile] : []),
...(options.offline ? ["--offline"] : []),
]
}
Loading
Loading