Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP Liveness Probe Stopgap Fix #232

Merged
merged 4 commits into from
Mar 20, 2025
Merged
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
5 changes: 5 additions & 0 deletions .changeset/warm-coins-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/service-core': patch
---

Minor improvement to the HTTP liveness probe. The liveness probe will return an HTTP `200` response code when running in the `API` mode. Any HTTP response in the `API` mode indicates the service is running. When running in the `UNIFIED` mode the HTTP response code is `200` if the last `touched_at` timestamp value is less than 10 seconds ago - this indicates the replication worker is running.
20 changes: 18 additions & 2 deletions packages/service-core/src/routes/endpoints/probes.ts
Original file line number Diff line number Diff line change
@@ -25,11 +25,27 @@ export const startupCheck = routeDefinition({
export const livenessCheck = routeDefinition({
path: ProbeRoutes.LIVENESS,
method: router.HTTPMethod.GET,
handler: async () => {
handler: async (params) => {
const state = container.probes.state();

/**
* The HTTP probes currently only function in the API and UNIFIED
* modes.
*
* For the API mode, we don't really touch the state, but any response from
* the request indicates the service is alive.
*
* For the UNIFIED mode we update the touched_at time while the Replicator engine is running.
* If the replication engine is present and the timeDifference from the last
* touched_at is large, we report that the service is not live.
*
* This is only an incremental improvement. In future these values should be configurable.
*/

const isAPIOnly = !params.context.service_context.replicationEngine;
const timeDifference = Date.now() - state.touched_at.getTime();
const status = timeDifference < 10000 ? 200 : 400;

const status = isAPIOnly ? 200 : timeDifference < 10000 ? 200 : 400;

return new router.RouterResponse({
status,
10 changes: 5 additions & 5 deletions packages/service-core/test/src/routes/probes.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import Fastify, { FastifyInstance } from 'fastify';
import { container } from '@powersync/lib-services-framework';
import * as auth from '../../../src/routes/auth.js';
import * as system from '../../../src/system/system-index.js';
import Fastify, { FastifyInstance } from 'fastify';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { configureFastifyServer } from '../../../src/index.js';
import * as auth from '../../../src/routes/auth.js';
import { ProbeRoutes } from '../../../src/routes/endpoints/probes.js';
import * as system from '../../../src/system/system-index.js';

vi.mock('@powersync/lib-services-framework', async () => {
const actual = (await vi.importActual('@powersync/lib-services-framework')) as any;
@@ -25,7 +25,7 @@ describe('Probe Routes Integration', () => {

beforeEach(async () => {
app = Fastify();
mockSystem = { routerEngine: {} } as system.ServiceContext;
mockSystem = { routerEngine: {}, replicationEngine: {} } as system.ServiceContext;
await configureFastifyServer(app, { service_context: mockSystem });
await app.ready();
});
9 changes: 5 additions & 4 deletions packages/service-core/test/src/routes/probes.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { container } from '@powersync/lib-services-framework';
import { startupCheck, livenessCheck, readinessCheck } from '../../../src/routes/endpoints/probes.js';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { livenessCheck, readinessCheck, startupCheck } from '../../../src/routes/endpoints/probes.js';

// Mock the container
vi.mock('@powersync/lib-services-framework', () => ({
@@ -83,6 +83,7 @@ describe('Probe Routes', () => {
});

describe('livenessCheck', () => {
const mockedContext = { context: { service_context: { replicationEngine: {} } } } as any;
it('has the correct route definitions', () => {
expect(livenessCheck.path).toBe('/probes/liveness');
expect(livenessCheck.method).toBe('GET');
@@ -97,7 +98,7 @@ describe('Probe Routes', () => {

vi.mocked(container.probes.state).mockReturnValue(mockState);

const response = await livenessCheck.handler();
const response = await livenessCheck.handler(mockedContext);

expect(response.status).toBe(200);
expect(response.data).toEqual(mockState);
@@ -112,7 +113,7 @@ describe('Probe Routes', () => {

vi.mocked(container.probes.state).mockReturnValue(mockState);

const response = await livenessCheck.handler();
const response = await livenessCheck.handler(mockedContext);

expect(response.status).toBe(400);
expect(response.data).toEqual(mockState);