Merge pull request #9069 from bluesky-social/migrate-blink
Start migrating blink. Also add better shutdown handling for ogcard
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
name: build-and-push-link-aws
|
name: build-and-push-link-aws
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'bskylink/**'
|
||||||
|
- 'Dockerfile.bskylink'
|
||||||
|
- '.github/workflows/build-and-push-link-aws.yaml'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
|
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
|
||||||
@@ -45,7 +50,7 @@ jobs:
|
|||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: true
|
||||||
file: ./Dockerfile.bskylink
|
file: ./Dockerfile.bskylink
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
+56
-23
@@ -1,29 +1,62 @@
|
|||||||
import {Database, envToCfg, httpLogger, LinkService, readEnv} from './index.js'
|
import {Database, envToCfg, httpLogger, LinkService, readEnv} from './index.js'
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const env = readEnv()
|
try {
|
||||||
const cfg = envToCfg(env)
|
httpLogger.info('Starting blink service')
|
||||||
if (cfg.db.migrationUrl) {
|
|
||||||
const migrateDb = Database.postgres({
|
const env = readEnv()
|
||||||
url: cfg.db.migrationUrl,
|
const cfg = envToCfg(env)
|
||||||
schema: cfg.db.schema,
|
|
||||||
|
httpLogger.info(
|
||||||
|
{
|
||||||
|
port: cfg.service.port,
|
||||||
|
safelinkEnabled: cfg.service.safelinkEnabled,
|
||||||
|
hasDbUrl: !!cfg.db.url,
|
||||||
|
hasDbMigrationUrl: !!cfg.db.migrationUrl,
|
||||||
|
},
|
||||||
|
'Configuration loaded',
|
||||||
|
)
|
||||||
|
|
||||||
|
if (cfg.db.migrationUrl) {
|
||||||
|
httpLogger.info('Running database migrations')
|
||||||
|
const migrateDb = Database.postgres({
|
||||||
|
url: cfg.db.migrationUrl,
|
||||||
|
schema: cfg.db.schema,
|
||||||
|
})
|
||||||
|
await migrateDb.migrateToLatestOrThrow()
|
||||||
|
await migrateDb.close()
|
||||||
|
httpLogger.info('Database migrations completed')
|
||||||
|
}
|
||||||
|
|
||||||
|
httpLogger.info('Creating LinkService')
|
||||||
|
const link = await LinkService.create(cfg)
|
||||||
|
|
||||||
|
if (link.ctx.cfg.service.safelinkEnabled) {
|
||||||
|
httpLogger.info('Starting Safelink client')
|
||||||
|
link.ctx.safelinkClient.runFetchEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
await link.start()
|
||||||
|
httpLogger.info('Link service is running')
|
||||||
|
|
||||||
|
process.on('SIGTERM', async () => {
|
||||||
|
httpLogger.info('Link service is stopping')
|
||||||
|
await link.destroy()
|
||||||
|
httpLogger.info('Link service is stopped')
|
||||||
})
|
})
|
||||||
await migrateDb.migrateToLatestOrThrow()
|
} catch (error) {
|
||||||
await migrateDb.close()
|
httpLogger.error(
|
||||||
|
{
|
||||||
|
error: String(error),
|
||||||
|
stack: error instanceof Error ? error.stack : undefined,
|
||||||
|
},
|
||||||
|
'Failed to start blink service',
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = await LinkService.create(cfg)
|
|
||||||
|
|
||||||
if (link.ctx.cfg.service.safelinkEnabled) {
|
|
||||||
link.ctx.safelinkClient.runFetchEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
await link.start()
|
|
||||||
httpLogger.info('link service is running')
|
|
||||||
process.on('SIGTERM', async () => {
|
|
||||||
httpLogger.info('link service is stopping')
|
|
||||||
await link.destroy()
|
|
||||||
httpLogger.info('link service is stopped')
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main().catch(error => {
|
||||||
|
console.error('Unhandled startup error:', error)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ export class Database {
|
|||||||
|
|
||||||
static postgres(opts: PgOptions): Database {
|
static postgres(opts: PgOptions): Database {
|
||||||
const {schema, url, txLockNonce} = opts
|
const {schema, url, txLockNonce} = opts
|
||||||
|
log.info(
|
||||||
|
{
|
||||||
|
schema,
|
||||||
|
poolSize: opts.poolSize,
|
||||||
|
poolMaxUses: opts.poolMaxUses,
|
||||||
|
poolIdleTimeoutMs: opts.poolIdleTimeoutMs,
|
||||||
|
},
|
||||||
|
'Creating database connection',
|
||||||
|
)
|
||||||
|
|
||||||
const pool =
|
const pool =
|
||||||
opts.pool ??
|
opts.pool ??
|
||||||
new Pg.Pool({
|
new Pg.Pool({
|
||||||
|
|||||||
+26
-4
@@ -62,7 +62,10 @@ export class CardService {
|
|||||||
// Start main application server
|
// Start main application server
|
||||||
this.server = this.app.listen(this.ctx.cfg.service.port)
|
this.server = this.app.listen(this.ctx.cfg.service.port)
|
||||||
this.server.keepAliveTimeout = 90000
|
this.server.keepAliveTimeout = 90000
|
||||||
this.terminator = createHttpTerminator({server: this.server})
|
this.terminator = createHttpTerminator({
|
||||||
|
server: this.server,
|
||||||
|
gracefulTerminationTimeout: 15000, // 15s timeout for in-flight requests
|
||||||
|
})
|
||||||
await events.once(this.server, 'listening')
|
await events.once(this.server, 'listening')
|
||||||
|
|
||||||
// Start separate metrics server
|
// Start separate metrics server
|
||||||
@@ -73,13 +76,32 @@ export class CardService {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.metricsServer = metricsApp.listen(this.ctx.cfg.service.metricsPort)
|
this.metricsServer = metricsApp.listen(this.ctx.cfg.service.metricsPort)
|
||||||
this.metricsTerminator = createHttpTerminator({server: this.metricsServer})
|
this.metricsTerminator = createHttpTerminator({
|
||||||
|
server: this.metricsServer,
|
||||||
|
gracefulTerminationTimeout: 2000, // 2s timeout for metrics server
|
||||||
|
})
|
||||||
await events.once(this.metricsServer, 'listening')
|
await events.once(this.metricsServer, 'listening')
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy() {
|
async destroy() {
|
||||||
|
const startTime = Date.now()
|
||||||
|
|
||||||
this.ctx.abortController.abort()
|
this.ctx.abortController.abort()
|
||||||
await this.terminator?.terminate()
|
|
||||||
await this.metricsTerminator?.terminate()
|
const shutdownPromises = []
|
||||||
|
|
||||||
|
if (this.terminator) {
|
||||||
|
shutdownPromises.push(this.terminator.terminate())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.metricsTerminator) {
|
||||||
|
shutdownPromises.push(this.metricsTerminator.terminate())
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(shutdownPromises)
|
||||||
|
|
||||||
|
const elapsed = Date.now() - startTime
|
||||||
|
const {httpLogger} = await import('./logger.js')
|
||||||
|
httpLogger.info(`Graceful shutdown completed in ${elapsed}ms`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user