diff --git a/.changeset/warm-coins-wave.md b/.changeset/warm-coins-wave.md new file mode 100644 index 00000000..7712bb0d --- /dev/null +++ b/.changeset/warm-coins-wave.md @@ -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. diff --git a/packages/service-core/src/routes/endpoints/probes.ts b/packages/service-core/src/routes/endpoints/probes.ts index 1d409532..46ea6fd2 100644 --- a/packages/service-core/src/routes/endpoints/probes.ts +++ b/packages/service-core/src/routes/endpoints/probes.ts @@ -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, diff --git a/packages/service-core/test/src/routes/probes.integration.test.ts b/packages/service-core/test/src/routes/probes.integration.test.ts index 7a3419e7..4532205a 100644 --- a/packages/service-core/test/src/routes/probes.integration.test.ts +++ b/packages/service-core/test/src/routes/probes.integration.test.ts @@ -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(); }); diff --git a/packages/service-core/test/src/routes/probes.test.ts b/packages/service-core/test/src/routes/probes.test.ts index 3433d950..dba88ca6 100644 --- a/packages/service-core/test/src/routes/probes.test.ts +++ b/packages/service-core/test/src/routes/probes.test.ts @@ -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);