Files
speckle-server/packages/server/modules/previews/index.ts
T

138 lines
4.4 KiB
TypeScript

/* istanbul ignore file */
import cron from 'node-cron'
import { moduleLogger, previewLogger as logger } from '@/observability/logging'
import { buildConsumePreviewResult } from '@/modules/previews/resultListener'
import {
disablePreviews,
getFeatureFlags,
getPreviewServiceRedisUrl,
getRedisUrl,
getServerOrigin
} from '@/modules/shared/helpers/envHelper'
import type { Queue } from 'bull'
import { ensureError } from '@speckle/shared'
import { previewRouterFactory } from '@/modules/previews/rest/router'
import type { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
import {
initializeMetrics,
observeMetricsFactory
} from '@/modules/previews/observability/metrics'
import {
requestActiveHandlerFactory,
requestErrorHandlerFactory,
requestFailedHandlerFactory
} from '@/modules/previews/queues/previews'
import { scheduleExecutionFactory } from '@/modules/core/services/taskScheduler'
import {
acquireTaskLockFactory,
releaseTaskLockFactory
} from '@/modules/core/repositories/scheduledTasks'
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
import { db } from '@/db/knex'
import { createRequestAndResponseQueues } from '@/modules/previews/clients/bull'
import { responseHandlerFactory } from '@/modules/previews/services/responses'
import { updateObjectPreviewFactory } from '@/modules/previews/repository/previews'
import type { BuildUpdateObjectPreview } from '@/modules/previews/domain/operations'
import { scheduleRetryFailedPreviews } from '@/modules/previews/tasks/tasks'
const { FF_RETRY_ERRORED_PREVIEWS_ENABLED } = getFeatureFlags()
let scheduledTasks: cron.ScheduledTask[] = []
const JobQueueName = 'preview-service-jobs'
const ResponseQueueNamePrefix = 'preview-service-results'
const buildUpdateObjectPreviewFunction =
(): BuildUpdateObjectPreview => async (params) => {
const { projectId } = params
const projectDb = await getProjectDbClient({ projectId })
return updateObjectPreviewFactory({ db: projectDb })
}
export const init: SpeckleModule['init'] = async ({
app,
isInitial,
metricsRegister
}) => {
if (!isInitial) return
if (disablePreviews()) {
moduleLogger.warn('📸 Object preview module is DISABLED')
return
} else {
moduleLogger.info('📸 Init object preview module')
}
const scheduleExecution = scheduleExecutionFactory({
acquireTaskLock: acquireTaskLockFactory({ db }),
releaseTaskLock: releaseTaskLockFactory({ db })
})
const responseQueueName = `${ResponseQueueNamePrefix}-${
new URL(getServerOrigin()).hostname
}`
let previewRequestQueue: Queue
let previewResponseQueue: Queue
try {
;({ requestQueue: previewRequestQueue, responseQueue: previewResponseQueue } =
await createRequestAndResponseQueues({
redisUrl: getPreviewServiceRedisUrl() ?? getRedisUrl(),
requestQueueName: JobQueueName,
responseQueueName,
requestErrorHandler: requestErrorHandlerFactory({ logger }),
requestFailedHandler: requestFailedHandlerFactory({
logger,
buildUpdateObjectPreview: buildUpdateObjectPreviewFunction()
}),
requestActiveHandler: requestActiveHandlerFactory({ logger })
}))
} catch (e) {
const err = ensureError(e, 'Unknown error when creating preview queues')
moduleLogger.error({ err }, 'Could not create preview queues. Disabling previews.')
return
}
scheduledTasks = [
...(FF_RETRY_ERRORED_PREVIEWS_ENABLED
? [
await scheduleRetryFailedPreviews({
scheduleExecution,
previewRequestQueue,
responseQueueName,
cronExpression: '*/23 * * * *' // every 23 minutes (kind of random prime number to reduce syncing with other possibly heavy tasks)
})
]
: [])
]
const { previewJobsProcessedSummary } = initializeMetrics({
registers: [metricsRegister],
previewRequestQueue,
previewResponseQueue
})
const previewRouter = previewRouterFactory({
previewRequestQueue,
responseQueueName
})
app.use(previewRouter)
void previewResponseQueue.process(
responseHandlerFactory({
observeMetrics: observeMetricsFactory({ summary: previewJobsProcessedSummary }),
logger,
consumePreviewResultBuilder: buildConsumePreviewResult
})
)
}
export const finalize = () => {}
export const shutdown: SpeckleModule['shutdown'] = async () => {
scheduledTasks.forEach((task) => {
task.stop()
})
}