@backstage/backend-defaults 0.5.1-next.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/auth/package.json +1 -1
- package/cache/package.json +1 -1
- package/database/package.json +1 -1
- package/discovery/package.json +1 -1
- package/dist/CreateBackend.cjs.js +49 -0
- package/dist/CreateBackend.cjs.js.map +1 -0
- package/dist/PackageDiscoveryService.cjs.js +109 -0
- package/dist/PackageDiscoveryService.cjs.js.map +1 -0
- package/dist/auth.cjs.js +2 -996
- package/dist/auth.cjs.js.map +1 -1
- package/dist/cache.cjs.js +4 -204
- package/dist/cache.cjs.js.map +1 -1
- package/dist/database.cjs.js +4 -957
- package/dist/database.cjs.js.map +1 -1
- package/dist/database.d.ts +4 -1
- package/dist/discovery.cjs.js +4 -92
- package/dist/discovery.cjs.js.map +1 -1
- package/dist/discoveryFeatureLoader.cjs.js +19 -0
- package/dist/discoveryFeatureLoader.cjs.js.map +1 -0
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js +130 -0
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -0
- package/dist/entrypoints/auth/JwksClient.cjs.js +49 -0
- package/dist/entrypoints/auth/JwksClient.cjs.js.map +1 -0
- package/dist/entrypoints/auth/authServiceFactory.cjs.js +57 -0
- package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/ExternalTokenHandler.cjs.js +78 -0
- package/dist/entrypoints/auth/external/ExternalTokenHandler.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/helpers.cjs.js +92 -0
- package/dist/entrypoints/auth/external/helpers.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/jwks.cjs.js +63 -0
- package/dist/entrypoints/auth/external/jwks.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/legacy.cjs.js +73 -0
- package/dist/entrypoints/auth/external/legacy.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/static.cjs.js +33 -0
- package/dist/entrypoints/auth/external/static.cjs.js.map +1 -0
- package/dist/{cjs/helpers-D2f1CG0o.cjs.js → entrypoints/auth/helpers.cjs.js} +33 -19
- package/dist/entrypoints/auth/helpers.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +147 -0
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js +73 -0
- package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js +75 -0
- package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js +91 -0
- package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/keys/createPluginKeySource.cjs.js +29 -0
- package/dist/entrypoints/auth/plugin/keys/createPluginKeySource.cjs.js.map +1 -0
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js +110 -0
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -0
- package/dist/entrypoints/cache/CacheClient.cjs.js +50 -0
- package/dist/entrypoints/cache/CacheClient.cjs.js.map +1 -0
- package/dist/entrypoints/cache/CacheManager.cjs.js +147 -0
- package/dist/entrypoints/cache/CacheManager.cjs.js.map +1 -0
- package/dist/entrypoints/cache/cacheServiceFactory.cjs.js +22 -0
- package/dist/entrypoints/cache/cacheServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/cache/types.cjs.js +10 -0
- package/dist/entrypoints/cache/types.cjs.js.map +1 -0
- package/dist/entrypoints/database/DatabaseManager.cjs.js +176 -0
- package/dist/entrypoints/database/DatabaseManager.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/defaultNameOverride.cjs.js +14 -0
- package/dist/entrypoints/database/connectors/defaultNameOverride.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/defaultSchemaOverride.cjs.js +12 -0
- package/dist/entrypoints/database/connectors/defaultSchemaOverride.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/mergeDatabaseConfig.cjs.js +10 -0
- package/dist/entrypoints/database/connectors/mergeDatabaseConfig.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/mysql.cjs.js +278 -0
- package/dist/entrypoints/database/connectors/mysql.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/postgres.cjs.js +304 -0
- package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/sqlite3.cjs.js +251 -0
- package/dist/entrypoints/database/connectors/sqlite3.cjs.js.map +1 -0
- package/dist/entrypoints/database/databaseServiceFactory.cjs.js +36 -0
- package/dist/entrypoints/database/databaseServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/discovery/HostDiscovery.cjs.js +86 -0
- package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -0
- package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js +17 -0
- package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js +192 -0
- package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/createAuthIntegrationRouter.cjs.js +19 -0
- package/dist/entrypoints/httpRouter/createAuthIntegrationRouter.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/createCookieAuthRefreshMiddleware.cjs.js +26 -0
- package/dist/entrypoints/httpRouter/createCookieAuthRefreshMiddleware.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/createCredentialsBarrier.cjs.js +63 -0
- package/dist/entrypoints/httpRouter/createCredentialsBarrier.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/createLifecycleMiddleware.cjs.js +52 -0
- package/dist/entrypoints/httpRouter/createLifecycleMiddleware.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js +48 -0
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js +88 -0
- package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/logger/loggerServiceFactory.cjs.js +17 -0
- package/dist/entrypoints/logger/loggerServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/permissions/permissionsServiceFactory.cjs.js +22 -0
- package/dist/entrypoints/permissions/permissionsServiceFactory.cjs.js.map +1 -0
- package/dist/{cjs/createConfigSecretEnumerator-DShyoWWL.cjs.js → entrypoints/rootConfig/createConfigSecretEnumerator.cjs.js} +1 -1
- package/dist/entrypoints/rootConfig/createConfigSecretEnumerator.cjs.js.map +1 -0
- package/dist/entrypoints/rootConfig/rootConfigServiceFactory.cjs.js +26 -0
- package/dist/entrypoints/rootConfig/rootConfigServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js +41 -0
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/DefaultRootHttpRouter.cjs.js +77 -0
- package/dist/entrypoints/rootHttpRouter/DefaultRootHttpRouter.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js +29 -0
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js +187 -0
- package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js +28 -0
- package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js.map +1 -0
- package/dist/{cjs/config-BDOwXIyo.cjs.js → entrypoints/rootHttpRouter/http/config.cjs.js} +1 -1
- package/dist/entrypoints/rootHttpRouter/http/config.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js +88 -0
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/getGeneratedCertificate.cjs.js +130 -0
- package/dist/entrypoints/rootHttpRouter/http/getGeneratedCertificate.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/readCorsOptions.cjs.js +51 -0
- package/dist/entrypoints/rootHttpRouter/http/readCorsOptions.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/readHelmetOptions.cjs.js +62 -0
- package/dist/entrypoints/rootHttpRouter/http/readHelmetOptions.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js +76 -0
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js +76 -0
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js +114 -0
- package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js.map +1 -0
- package/dist/entrypoints/rootLogger/rootLoggerServiceFactory.cjs.js +30 -0
- package/dist/entrypoints/rootLogger/rootLoggerServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/database/migrateBackendTasks.cjs.js +18 -0
- package/dist/entrypoints/scheduler/database/migrateBackendTasks.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/database/tables.cjs.js +8 -0
- package/dist/entrypoints/scheduler/database/tables.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js +37 -0
- package/dist/entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js +105 -0
- package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js +138 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.cjs.js +59 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js +275 -0
- package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/types.cjs.js +60 -0
- package/dist/entrypoints/scheduler/lib/types.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/util.cjs.js +66 -0
- package/dist/entrypoints/scheduler/lib/util.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/schedulerServiceFactory.cjs.js +19 -0
- package/dist/entrypoints/scheduler/schedulerServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js +274 -0
- package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js +261 -0
- package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js +148 -0
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js +174 -0
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +170 -0
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js +182 -0
- package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +132 -0
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js +147 -0
- package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js +122 -0
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js +226 -0
- package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +276 -0
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js +129 -0
- package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js +120 -0
- package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js +49 -0
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/UrlReaderPredicateMux.cjs.js +46 -0
- package/dist/entrypoints/urlReader/lib/UrlReaderPredicateMux.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js +68 -0
- package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js +46 -0
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js +78 -0
- package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js +147 -0
- package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js +161 -0
- package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/util.cjs.js +28 -0
- package/dist/entrypoints/urlReader/lib/tree/util.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/util.cjs.js +11 -0
- package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/urlReaderServiceFactory.cjs.js +29 -0
- package/dist/entrypoints/urlReader/urlReaderServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js +59 -0
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js.map +1 -0
- package/dist/entrypoints/userInfo/userInfoServiceFactory.cjs.js +17 -0
- package/dist/entrypoints/userInfo/userInfoServiceFactory.cjs.js.map +1 -0
- package/dist/httpAuth.cjs.js +3 -187
- package/dist/httpAuth.cjs.js.map +1 -1
- package/dist/httpRouter.cjs.js +2 -166
- package/dist/httpRouter.cjs.js.map +1 -1
- package/dist/index.cjs.js +4 -160
- package/dist/index.cjs.js.map +1 -1
- package/dist/lib/escapeRegExp.cjs.js +8 -0
- package/dist/lib/escapeRegExp.cjs.js.map +1 -0
- package/dist/lifecycle.cjs.js +3 -58
- package/dist/lifecycle.cjs.js.map +1 -1
- package/dist/logger.cjs.js +3 -12
- package/dist/logger.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +252 -0
- package/dist/package.json.cjs.js.map +1 -0
- package/dist/permissions.cjs.js +3 -17
- package/dist/permissions.cjs.js.map +1 -1
- package/dist/rootConfig.cjs.js +4 -22
- package/dist/rootConfig.cjs.js.map +1 -1
- package/dist/rootHealth.cjs.js +3 -35
- package/dist/rootHealth.cjs.js.map +1 -1
- package/dist/rootHttpRouter.cjs.js +15 -651
- package/dist/rootHttpRouter.cjs.js.map +1 -1
- package/dist/rootLifecycle.cjs.js +3 -70
- package/dist/rootLifecycle.cjs.js.map +1 -1
- package/dist/rootLogger.cjs.js +4 -137
- package/dist/rootLogger.cjs.js.map +1 -1
- package/dist/scheduler.cjs.js +4 -693
- package/dist/scheduler.cjs.js.map +1 -1
- package/dist/scheduler.d.ts +2 -1
- package/dist/urlReader.cjs.js +32 -2962
- package/dist/urlReader.cjs.js.map +1 -1
- package/dist/urlReader.d.ts +1 -2
- package/dist/userInfo.cjs.js +2 -64
- package/dist/userInfo.cjs.js.map +1 -1
- package/httpAuth/package.json +1 -1
- package/httpRouter/package.json +1 -1
- package/lifecycle/package.json +1 -1
- package/logger/package.json +1 -1
- package/package.json +20 -20
- package/permissions/package.json +1 -1
- package/rootConfig/package.json +1 -1
- package/rootHealth/package.json +1 -1
- package/rootHttpRouter/package.json +1 -1
- package/rootLifecycle/package.json +1 -1
- package/rootLogger/package.json +1 -1
- package/scheduler/package.json +1 -1
- package/urlReader/package.json +1 -1
- package/userInfo/package.json +1 -1
- package/dist/cjs/config-BDOwXIyo.cjs.js.map +0 -1
- package/dist/cjs/createConfigSecretEnumerator-DShyoWWL.cjs.js.map +0 -1
- package/dist/cjs/helpers-D2f1CG0o.cjs.js.map +0 -1
package/dist/scheduler.cjs.js
CHANGED
|
@@ -1,699 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
|
-
var api = require('@opentelemetry/api');
|
|
7
|
-
var errors = require('@backstage/errors');
|
|
8
|
-
var cron = require('cron');
|
|
9
|
-
var uuid = require('uuid');
|
|
10
|
-
var zod = require('zod');
|
|
3
|
+
var DefaultSchedulerService = require('./entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js');
|
|
4
|
+
var schedulerServiceFactory = require('./entrypoints/scheduler/schedulerServiceFactory.cjs.js');
|
|
11
5
|
|
|
12
|
-
const DB_MIGRATIONS_TABLE = "backstage_backend_tasks__knex_migrations";
|
|
13
|
-
const DB_TASKS_TABLE = "backstage_backend_tasks__tasks";
|
|
14
6
|
|
|
15
|
-
async function migrateBackendTasks(knex) {
|
|
16
|
-
const migrationsDir = backendPluginApi.resolvePackagePath(
|
|
17
|
-
"@backstage/backend-defaults",
|
|
18
|
-
"migrations/scheduler"
|
|
19
|
-
);
|
|
20
|
-
await knex.migrate.latest({
|
|
21
|
-
directory: migrationsDir,
|
|
22
|
-
tableName: DB_MIGRATIONS_TABLE
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
7
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (typeof id !== "string" || !id.trim()) {
|
|
29
|
-
throw new errors.InputError(`${id} is not a valid ID, expected non-empty string`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function nowPlus(duration, knex) {
|
|
33
|
-
const seconds = duration?.as("seconds") ?? 0;
|
|
34
|
-
if (!seconds) {
|
|
35
|
-
return knex.fn.now();
|
|
36
|
-
}
|
|
37
|
-
if (knex.client.config.client.includes("sqlite3")) {
|
|
38
|
-
return knex.raw(`datetime('now', ?)`, [`${seconds} seconds`]);
|
|
39
|
-
}
|
|
40
|
-
if (knex.client.config.client.includes("mysql")) {
|
|
41
|
-
return knex.raw(`now() + interval ${seconds} second`);
|
|
42
|
-
}
|
|
43
|
-
return knex.raw(`now() + interval '${seconds} seconds'`);
|
|
44
|
-
}
|
|
45
|
-
async function sleep(duration, abortSignal) {
|
|
46
|
-
if (abortSignal?.aborted) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
await new Promise((resolve) => {
|
|
50
|
-
let timeoutHandle = void 0;
|
|
51
|
-
const done = () => {
|
|
52
|
-
if (timeoutHandle) {
|
|
53
|
-
clearTimeout(timeoutHandle);
|
|
54
|
-
}
|
|
55
|
-
abortSignal?.removeEventListener("abort", done);
|
|
56
|
-
resolve();
|
|
57
|
-
};
|
|
58
|
-
timeoutHandle = setTimeout(done, duration.as("milliseconds"));
|
|
59
|
-
abortSignal?.addEventListener("abort", done);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
function delegateAbortController(parent) {
|
|
63
|
-
const delegate = new AbortController();
|
|
64
|
-
if (parent) {
|
|
65
|
-
if (parent.aborted) {
|
|
66
|
-
delegate.abort();
|
|
67
|
-
} else {
|
|
68
|
-
const onParentAborted = () => {
|
|
69
|
-
delegate.abort();
|
|
70
|
-
};
|
|
71
|
-
const onChildAborted = () => {
|
|
72
|
-
parent.removeEventListener("abort", onParentAborted);
|
|
73
|
-
};
|
|
74
|
-
parent.addEventListener("abort", onParentAborted, { once: true });
|
|
75
|
-
delegate.signal.addEventListener("abort", onChildAborted, { once: true });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return delegate;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
class LocalTaskWorker {
|
|
82
|
-
constructor(taskId, fn, logger) {
|
|
83
|
-
this.taskId = taskId;
|
|
84
|
-
this.fn = fn;
|
|
85
|
-
this.logger = logger;
|
|
86
|
-
}
|
|
87
|
-
abortWait;
|
|
88
|
-
start(settings, options) {
|
|
89
|
-
this.logger.info(
|
|
90
|
-
`Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`
|
|
91
|
-
);
|
|
92
|
-
(async () => {
|
|
93
|
-
let attemptNum = 1;
|
|
94
|
-
for (; ; ) {
|
|
95
|
-
try {
|
|
96
|
-
if (settings.initialDelayDuration) {
|
|
97
|
-
await this.sleep(
|
|
98
|
-
luxon.Duration.fromISO(settings.initialDelayDuration),
|
|
99
|
-
options?.signal
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
while (!options?.signal?.aborted) {
|
|
103
|
-
const startTime = process.hrtime();
|
|
104
|
-
await this.runOnce(settings, options?.signal);
|
|
105
|
-
const timeTaken = process.hrtime(startTime);
|
|
106
|
-
await this.waitUntilNext(
|
|
107
|
-
settings,
|
|
108
|
-
(timeTaken[0] + timeTaken[1] / 1e9) * 1e3,
|
|
109
|
-
options?.signal
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
this.logger.info(`Task worker finished: ${this.taskId}`);
|
|
113
|
-
attemptNum = 0;
|
|
114
|
-
break;
|
|
115
|
-
} catch (e) {
|
|
116
|
-
attemptNum += 1;
|
|
117
|
-
this.logger.warn(
|
|
118
|
-
`Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`
|
|
119
|
-
);
|
|
120
|
-
await sleep(luxon.Duration.fromObject({ seconds: 1 }));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
})();
|
|
124
|
-
}
|
|
125
|
-
trigger() {
|
|
126
|
-
if (!this.abortWait) {
|
|
127
|
-
throw new errors.ConflictError(`Task ${this.taskId} is currently running`);
|
|
128
|
-
}
|
|
129
|
-
this.abortWait.abort();
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Makes a single attempt at running the task to completion.
|
|
133
|
-
*/
|
|
134
|
-
async runOnce(settings, signal) {
|
|
135
|
-
const taskAbortController = delegateAbortController(signal);
|
|
136
|
-
const timeoutHandle = setTimeout(() => {
|
|
137
|
-
taskAbortController.abort();
|
|
138
|
-
}, luxon.Duration.fromISO(settings.timeoutAfterDuration).as("milliseconds"));
|
|
139
|
-
try {
|
|
140
|
-
await this.fn(taskAbortController.signal);
|
|
141
|
-
} catch (e) {
|
|
142
|
-
}
|
|
143
|
-
clearTimeout(timeoutHandle);
|
|
144
|
-
taskAbortController.abort();
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Sleeps until it's time to run the task again.
|
|
148
|
-
*/
|
|
149
|
-
async waitUntilNext(settings, lastRunMillis, signal) {
|
|
150
|
-
if (signal?.aborted) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const isCron = !settings.cadence.startsWith("P");
|
|
154
|
-
let dt;
|
|
155
|
-
if (isCron) {
|
|
156
|
-
const nextRun = +new cron.CronTime(settings.cadence).sendAt().toJSDate();
|
|
157
|
-
dt = nextRun - Date.now();
|
|
158
|
-
} else {
|
|
159
|
-
dt = luxon.Duration.fromISO(settings.cadence).as("milliseconds") - lastRunMillis;
|
|
160
|
-
}
|
|
161
|
-
dt = Math.max(dt, 0);
|
|
162
|
-
this.logger.debug(
|
|
163
|
-
`task: ${this.taskId} will next occur around ${luxon.DateTime.now().plus(
|
|
164
|
-
luxon.Duration.fromMillis(dt)
|
|
165
|
-
)}`
|
|
166
|
-
);
|
|
167
|
-
await this.sleep(luxon.Duration.fromMillis(dt), signal);
|
|
168
|
-
}
|
|
169
|
-
async sleep(duration, abortSignal) {
|
|
170
|
-
this.abortWait = delegateAbortController(abortSignal);
|
|
171
|
-
await sleep(duration, this.abortWait.signal);
|
|
172
|
-
this.abortWait.abort();
|
|
173
|
-
this.abortWait = void 0;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function isValidOptionalDurationString(d) {
|
|
178
|
-
try {
|
|
179
|
-
return !d || luxon.Duration.fromISO(d).isValid;
|
|
180
|
-
} catch {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
function isValidCronFormat(c) {
|
|
185
|
-
try {
|
|
186
|
-
if (!c) {
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
new cron.CronTime(c);
|
|
190
|
-
return true;
|
|
191
|
-
} catch {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function isValidTrigger(t) {
|
|
196
|
-
return t === "manual";
|
|
197
|
-
}
|
|
198
|
-
zod.z.object({
|
|
199
|
-
version: zod.z.literal(1),
|
|
200
|
-
initialDelayDuration: zod.z.string().optional().refine(isValidOptionalDurationString, {
|
|
201
|
-
message: "Invalid duration, expecting ISO Period"
|
|
202
|
-
}),
|
|
203
|
-
recurringAtMostEveryDuration: zod.z.string().refine(isValidOptionalDurationString, {
|
|
204
|
-
message: "Invalid duration, expecting ISO Period"
|
|
205
|
-
}),
|
|
206
|
-
timeoutAfterDuration: zod.z.string().refine(isValidOptionalDurationString, {
|
|
207
|
-
message: "Invalid duration, expecting ISO Period"
|
|
208
|
-
})
|
|
209
|
-
});
|
|
210
|
-
const taskSettingsV2Schema = zod.z.object({
|
|
211
|
-
version: zod.z.literal(2),
|
|
212
|
-
cadence: zod.z.string().refine(isValidCronFormat, { message: "Invalid cron" }).or(
|
|
213
|
-
zod.z.string().refine(isValidTrigger, {
|
|
214
|
-
message: "Invalid trigger, expecting 'manual'"
|
|
215
|
-
})
|
|
216
|
-
).or(
|
|
217
|
-
zod.z.string().refine(isValidOptionalDurationString, {
|
|
218
|
-
message: "Invalid duration, expecting ISO Period"
|
|
219
|
-
})
|
|
220
|
-
),
|
|
221
|
-
timeoutAfterDuration: zod.z.string().refine(isValidOptionalDurationString, {
|
|
222
|
-
message: "Invalid duration, expecting ISO Period"
|
|
223
|
-
}),
|
|
224
|
-
initialDelayDuration: zod.z.string().optional().refine(isValidOptionalDurationString, {
|
|
225
|
-
message: "Invalid duration, expecting ISO Period"
|
|
226
|
-
})
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const DEFAULT_WORK_CHECK_FREQUENCY = luxon.Duration.fromObject({ seconds: 5 });
|
|
230
|
-
class TaskWorker {
|
|
231
|
-
constructor(taskId, fn, knex, logger, workCheckFrequency = DEFAULT_WORK_CHECK_FREQUENCY) {
|
|
232
|
-
this.taskId = taskId;
|
|
233
|
-
this.fn = fn;
|
|
234
|
-
this.knex = knex;
|
|
235
|
-
this.logger = logger;
|
|
236
|
-
this.workCheckFrequency = workCheckFrequency;
|
|
237
|
-
}
|
|
238
|
-
async start(settings, options) {
|
|
239
|
-
try {
|
|
240
|
-
await this.persistTask(settings);
|
|
241
|
-
} catch (e) {
|
|
242
|
-
throw new Error(`Failed to persist task, ${e}`);
|
|
243
|
-
}
|
|
244
|
-
this.logger.info(
|
|
245
|
-
`Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`
|
|
246
|
-
);
|
|
247
|
-
let workCheckFrequency = this.workCheckFrequency;
|
|
248
|
-
const isDuration = settings?.cadence.startsWith("P");
|
|
249
|
-
if (isDuration) {
|
|
250
|
-
const cadence = luxon.Duration.fromISO(settings.cadence);
|
|
251
|
-
if (cadence < workCheckFrequency) {
|
|
252
|
-
workCheckFrequency = cadence;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
let attemptNum = 1;
|
|
256
|
-
(async () => {
|
|
257
|
-
for (; ; ) {
|
|
258
|
-
try {
|
|
259
|
-
if (settings.initialDelayDuration) {
|
|
260
|
-
await sleep(
|
|
261
|
-
luxon.Duration.fromISO(settings.initialDelayDuration),
|
|
262
|
-
options?.signal
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
while (!options?.signal?.aborted) {
|
|
266
|
-
const runResult = await this.runOnce(options?.signal);
|
|
267
|
-
if (runResult.result === "abort") {
|
|
268
|
-
break;
|
|
269
|
-
}
|
|
270
|
-
await sleep(workCheckFrequency, options?.signal);
|
|
271
|
-
}
|
|
272
|
-
this.logger.info(`Task worker finished: ${this.taskId}`);
|
|
273
|
-
attemptNum = 0;
|
|
274
|
-
break;
|
|
275
|
-
} catch (e) {
|
|
276
|
-
attemptNum += 1;
|
|
277
|
-
this.logger.warn(
|
|
278
|
-
`Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`
|
|
279
|
-
);
|
|
280
|
-
await sleep(luxon.Duration.fromObject({ seconds: 1 }));
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
})();
|
|
284
|
-
}
|
|
285
|
-
static async trigger(knex, taskId) {
|
|
286
|
-
const rows = await knex(DB_TASKS_TABLE).select(knex.raw(1)).where("id", "=", taskId);
|
|
287
|
-
if (rows.length !== 1) {
|
|
288
|
-
throw new errors.NotFoundError(`Task ${taskId} does not exist`);
|
|
289
|
-
}
|
|
290
|
-
const updatedRows = await knex(DB_TASKS_TABLE).where("id", "=", taskId).whereNull("current_run_ticket").update({
|
|
291
|
-
next_run_start_at: knex.fn.now()
|
|
292
|
-
});
|
|
293
|
-
if (updatedRows < 1) {
|
|
294
|
-
throw new errors.ConflictError(`Task ${taskId} is currently running`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Makes a single attempt at running the task to completion, if ready.
|
|
299
|
-
*
|
|
300
|
-
* @returns The outcome of the attempt
|
|
301
|
-
*/
|
|
302
|
-
async runOnce(signal) {
|
|
303
|
-
const findResult = await this.findReadyTask();
|
|
304
|
-
if (findResult.result === "not-ready-yet" || findResult.result === "abort") {
|
|
305
|
-
return findResult;
|
|
306
|
-
}
|
|
307
|
-
const taskSettings = findResult.settings;
|
|
308
|
-
const ticket = uuid.v4();
|
|
309
|
-
const claimed = await this.tryClaimTask(ticket, taskSettings);
|
|
310
|
-
if (!claimed) {
|
|
311
|
-
return { result: "not-ready-yet" };
|
|
312
|
-
}
|
|
313
|
-
const taskAbortController = delegateAbortController(signal);
|
|
314
|
-
const timeoutHandle = setTimeout(() => {
|
|
315
|
-
taskAbortController.abort();
|
|
316
|
-
}, luxon.Duration.fromISO(taskSettings.timeoutAfterDuration).as("milliseconds"));
|
|
317
|
-
try {
|
|
318
|
-
await this.fn(taskAbortController.signal);
|
|
319
|
-
taskAbortController.abort();
|
|
320
|
-
} catch (e) {
|
|
321
|
-
this.logger.error(e);
|
|
322
|
-
await this.tryReleaseTask(ticket, taskSettings);
|
|
323
|
-
return { result: "failed" };
|
|
324
|
-
} finally {
|
|
325
|
-
clearTimeout(timeoutHandle);
|
|
326
|
-
}
|
|
327
|
-
await this.tryReleaseTask(ticket, taskSettings);
|
|
328
|
-
return { result: "completed" };
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Perform the initial store of the task info
|
|
332
|
-
*/
|
|
333
|
-
async persistTask(settings) {
|
|
334
|
-
taskSettingsV2Schema.parse(settings);
|
|
335
|
-
const isManual = settings?.cadence === "manual";
|
|
336
|
-
const isDuration = settings?.cadence.startsWith("P");
|
|
337
|
-
const isCron = !isManual && !isDuration;
|
|
338
|
-
let startAt;
|
|
339
|
-
let nextStartAt;
|
|
340
|
-
if (settings.initialDelayDuration) {
|
|
341
|
-
startAt = nowPlus(
|
|
342
|
-
luxon.Duration.fromISO(settings.initialDelayDuration),
|
|
343
|
-
this.knex
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
if (isCron) {
|
|
347
|
-
const time = new cron.CronTime(settings.cadence).sendAt().minus({ seconds: 1 }).toUTC();
|
|
348
|
-
nextStartAt = this.nextRunAtRaw(time);
|
|
349
|
-
startAt ||= nextStartAt;
|
|
350
|
-
} else if (isManual) {
|
|
351
|
-
nextStartAt = this.knex.raw("null");
|
|
352
|
-
startAt ||= nextStartAt;
|
|
353
|
-
} else {
|
|
354
|
-
startAt ||= this.knex.fn.now();
|
|
355
|
-
nextStartAt = nowPlus(luxon.Duration.fromISO(settings.cadence), this.knex);
|
|
356
|
-
}
|
|
357
|
-
this.logger.debug(`task: ${this.taskId} configured to run at: ${startAt}`);
|
|
358
|
-
const settingsJson = JSON.stringify(settings);
|
|
359
|
-
await this.knex(DB_TASKS_TABLE).insert({
|
|
360
|
-
id: this.taskId,
|
|
361
|
-
settings_json: settingsJson,
|
|
362
|
-
next_run_start_at: startAt
|
|
363
|
-
}).onConflict("id").merge(
|
|
364
|
-
this.knex.client.config.client.includes("mysql") ? {
|
|
365
|
-
settings_json: settingsJson,
|
|
366
|
-
next_run_start_at: this.knex.raw(
|
|
367
|
-
`CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,
|
|
368
|
-
[
|
|
369
|
-
nextStartAt,
|
|
370
|
-
"next_run_start_at",
|
|
371
|
-
nextStartAt,
|
|
372
|
-
"next_run_start_at"
|
|
373
|
-
]
|
|
374
|
-
)
|
|
375
|
-
} : {
|
|
376
|
-
settings_json: this.knex.ref("excluded.settings_json"),
|
|
377
|
-
next_run_start_at: this.knex.raw(
|
|
378
|
-
`CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,
|
|
379
|
-
[
|
|
380
|
-
nextStartAt,
|
|
381
|
-
`${DB_TASKS_TABLE}.next_run_start_at`,
|
|
382
|
-
nextStartAt,
|
|
383
|
-
`${DB_TASKS_TABLE}.next_run_start_at`
|
|
384
|
-
]
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Check if the task is ready to run
|
|
391
|
-
*/
|
|
392
|
-
async findReadyTask() {
|
|
393
|
-
const [row] = await this.knex(DB_TASKS_TABLE).where("id", "=", this.taskId).select({
|
|
394
|
-
settingsJson: "settings_json",
|
|
395
|
-
ready: this.knex.raw(
|
|
396
|
-
`CASE
|
|
397
|
-
WHEN next_run_start_at <= ? AND current_run_ticket IS NULL THEN TRUE
|
|
398
|
-
ELSE FALSE
|
|
399
|
-
END`,
|
|
400
|
-
[this.knex.fn.now()]
|
|
401
|
-
)
|
|
402
|
-
});
|
|
403
|
-
if (!row) {
|
|
404
|
-
this.logger.info(
|
|
405
|
-
"No longer able to find task; aborting and assuming that it has been unregistered or expired"
|
|
406
|
-
);
|
|
407
|
-
return { result: "abort" };
|
|
408
|
-
} else if (!row.ready) {
|
|
409
|
-
return { result: "not-ready-yet" };
|
|
410
|
-
}
|
|
411
|
-
try {
|
|
412
|
-
const obj = JSON.parse(row.settingsJson);
|
|
413
|
-
const settings = taskSettingsV2Schema.parse(obj);
|
|
414
|
-
return { result: "ready", settings };
|
|
415
|
-
} catch (e) {
|
|
416
|
-
this.logger.info(
|
|
417
|
-
`Task "${this.taskId}" is no longer able to parse task settings; aborting and assuming that a newer version of the task has been issued and being handled by other workers, ${e}`
|
|
418
|
-
);
|
|
419
|
-
return { result: "abort" };
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Attempts to claim a task that's ready for execution, on this worker's
|
|
424
|
-
* behalf. We should not attempt to perform the work unless the claim really
|
|
425
|
-
* goes through.
|
|
426
|
-
*
|
|
427
|
-
* @param ticket - A globally unique string that changes for each invocation
|
|
428
|
-
* @param settings - The settings of the task to claim
|
|
429
|
-
* @returns True if it was successfully claimed
|
|
430
|
-
*/
|
|
431
|
-
async tryClaimTask(ticket, settings) {
|
|
432
|
-
const startedAt = this.knex.fn.now();
|
|
433
|
-
const expiresAt = settings.timeoutAfterDuration ? nowPlus(luxon.Duration.fromISO(settings.timeoutAfterDuration), this.knex) : this.knex.raw("null");
|
|
434
|
-
const rows = await this.knex(DB_TASKS_TABLE).where("id", "=", this.taskId).whereNull("current_run_ticket").update({
|
|
435
|
-
current_run_ticket: ticket,
|
|
436
|
-
current_run_started_at: startedAt,
|
|
437
|
-
current_run_expires_at: expiresAt
|
|
438
|
-
});
|
|
439
|
-
return rows === 1;
|
|
440
|
-
}
|
|
441
|
-
async tryReleaseTask(ticket, settings) {
|
|
442
|
-
const isManual = settings?.cadence === "manual";
|
|
443
|
-
const isDuration = settings?.cadence.startsWith("P");
|
|
444
|
-
const isCron = !isManual && !isDuration;
|
|
445
|
-
let nextRun;
|
|
446
|
-
if (isCron) {
|
|
447
|
-
const time = new cron.CronTime(settings.cadence).sendAt().toUTC();
|
|
448
|
-
this.logger.debug(`task: ${this.taskId} will next occur around ${time}`);
|
|
449
|
-
nextRun = this.nextRunAtRaw(time);
|
|
450
|
-
} else if (isManual) {
|
|
451
|
-
nextRun = this.knex.raw("null");
|
|
452
|
-
} else {
|
|
453
|
-
const dt = luxon.Duration.fromISO(settings.cadence).as("seconds");
|
|
454
|
-
this.logger.debug(
|
|
455
|
-
`task: ${this.taskId} will next occur around ${luxon.DateTime.now().plus({
|
|
456
|
-
seconds: dt
|
|
457
|
-
})}`
|
|
458
|
-
);
|
|
459
|
-
if (this.knex.client.config.client.includes("sqlite3")) {
|
|
460
|
-
nextRun = this.knex.raw(
|
|
461
|
-
`max(datetime(next_run_start_at, ?), datetime('now'))`,
|
|
462
|
-
[`+${dt} seconds`]
|
|
463
|
-
);
|
|
464
|
-
} else if (this.knex.client.config.client.includes("mysql")) {
|
|
465
|
-
nextRun = this.knex.raw(
|
|
466
|
-
`greatest(next_run_start_at + interval ${dt} second, now())`
|
|
467
|
-
);
|
|
468
|
-
} else {
|
|
469
|
-
nextRun = this.knex.raw(
|
|
470
|
-
`greatest(next_run_start_at + interval '${dt} seconds', now())`
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
const rows = await this.knex(DB_TASKS_TABLE).where("id", "=", this.taskId).where("current_run_ticket", "=", ticket).update({
|
|
475
|
-
next_run_start_at: nextRun,
|
|
476
|
-
current_run_ticket: this.knex.raw("null"),
|
|
477
|
-
current_run_started_at: this.knex.raw("null"),
|
|
478
|
-
current_run_expires_at: this.knex.raw("null")
|
|
479
|
-
});
|
|
480
|
-
return rows === 1;
|
|
481
|
-
}
|
|
482
|
-
nextRunAtRaw(time) {
|
|
483
|
-
if (this.knex.client.config.client.includes("sqlite3")) {
|
|
484
|
-
return this.knex.raw("datetime(?)", [time.toISO()]);
|
|
485
|
-
} else if (this.knex.client.config.client.includes("mysql")) {
|
|
486
|
-
return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]);
|
|
487
|
-
}
|
|
488
|
-
return this.knex.raw(`?`, [time.toISO()]);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const tracer = api.trace.getTracer(TRACER_ID);
|
|
493
|
-
class PluginTaskSchedulerImpl {
|
|
494
|
-
constructor(databaseFactory, logger) {
|
|
495
|
-
this.databaseFactory = databaseFactory;
|
|
496
|
-
this.logger = logger;
|
|
497
|
-
const meter = api.metrics.getMeter("default");
|
|
498
|
-
this.counter = meter.createCounter("backend_tasks.task.runs.count", {
|
|
499
|
-
description: "Total number of times a task has been run"
|
|
500
|
-
});
|
|
501
|
-
this.duration = meter.createHistogram("backend_tasks.task.runs.duration", {
|
|
502
|
-
description: "Histogram of task run durations",
|
|
503
|
-
unit: "seconds"
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
localTasksById = /* @__PURE__ */ new Map();
|
|
507
|
-
allScheduledTasks = [];
|
|
508
|
-
counter;
|
|
509
|
-
duration;
|
|
510
|
-
async triggerTask(id) {
|
|
511
|
-
const localTask = this.localTasksById.get(id);
|
|
512
|
-
if (localTask) {
|
|
513
|
-
localTask.trigger();
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const knex = await this.databaseFactory();
|
|
517
|
-
await TaskWorker.trigger(knex, id);
|
|
518
|
-
}
|
|
519
|
-
async scheduleTask(task) {
|
|
520
|
-
validateId(task.id);
|
|
521
|
-
const scope = task.scope ?? "global";
|
|
522
|
-
const settings = {
|
|
523
|
-
version: 2,
|
|
524
|
-
cadence: parseDuration(task.frequency),
|
|
525
|
-
initialDelayDuration: task.initialDelay && parseDuration(task.initialDelay),
|
|
526
|
-
timeoutAfterDuration: parseDuration(task.timeout)
|
|
527
|
-
};
|
|
528
|
-
if (scope === "global") {
|
|
529
|
-
const knex = await this.databaseFactory();
|
|
530
|
-
const worker = new TaskWorker(
|
|
531
|
-
task.id,
|
|
532
|
-
this.instrumentedFunction(task, scope),
|
|
533
|
-
knex,
|
|
534
|
-
this.logger.child({ task: task.id })
|
|
535
|
-
);
|
|
536
|
-
await worker.start(settings, { signal: task.signal });
|
|
537
|
-
} else {
|
|
538
|
-
const worker = new LocalTaskWorker(
|
|
539
|
-
task.id,
|
|
540
|
-
this.instrumentedFunction(task, scope),
|
|
541
|
-
this.logger.child({ task: task.id })
|
|
542
|
-
);
|
|
543
|
-
worker.start(settings, { signal: task.signal });
|
|
544
|
-
this.localTasksById.set(task.id, worker);
|
|
545
|
-
}
|
|
546
|
-
this.allScheduledTasks.push({
|
|
547
|
-
id: task.id,
|
|
548
|
-
scope,
|
|
549
|
-
settings
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
createScheduledTaskRunner(schedule) {
|
|
553
|
-
return {
|
|
554
|
-
run: async (task) => {
|
|
555
|
-
await this.scheduleTask({ ...task, ...schedule });
|
|
556
|
-
}
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
async getScheduledTasks() {
|
|
560
|
-
return this.allScheduledTasks;
|
|
561
|
-
}
|
|
562
|
-
instrumentedFunction(task, scope) {
|
|
563
|
-
return async (abort) => {
|
|
564
|
-
const labels = {
|
|
565
|
-
taskId: task.id,
|
|
566
|
-
scope
|
|
567
|
-
};
|
|
568
|
-
this.counter.add(1, { ...labels, result: "started" });
|
|
569
|
-
const startTime = process.hrtime();
|
|
570
|
-
try {
|
|
571
|
-
await tracer.startActiveSpan(`task ${task.id}`, async (span) => {
|
|
572
|
-
try {
|
|
573
|
-
span.setAttributes(labels);
|
|
574
|
-
await task.fn(abort);
|
|
575
|
-
} catch (error) {
|
|
576
|
-
if (error instanceof Error) {
|
|
577
|
-
span.recordException(error);
|
|
578
|
-
}
|
|
579
|
-
throw error;
|
|
580
|
-
} finally {
|
|
581
|
-
span.end();
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
labels.result = "completed";
|
|
585
|
-
} catch (ex) {
|
|
586
|
-
labels.result = "failed";
|
|
587
|
-
throw ex;
|
|
588
|
-
} finally {
|
|
589
|
-
const delta = process.hrtime(startTime);
|
|
590
|
-
const endTime = delta[0] + delta[1] / 1e9;
|
|
591
|
-
this.counter.add(1, labels);
|
|
592
|
-
this.duration.record(endTime, labels);
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
function parseDuration(frequency) {
|
|
598
|
-
if (typeof frequency === "object" && "cron" in frequency) {
|
|
599
|
-
return frequency.cron;
|
|
600
|
-
}
|
|
601
|
-
if (typeof frequency === "object" && "trigger" in frequency) {
|
|
602
|
-
return frequency.trigger;
|
|
603
|
-
}
|
|
604
|
-
const parsed = luxon.Duration.isDuration(frequency) ? frequency : luxon.Duration.fromObject(frequency);
|
|
605
|
-
if (!parsed.isValid) {
|
|
606
|
-
throw new Error(
|
|
607
|
-
`Invalid duration, ${parsed.invalidReason}: ${parsed.invalidExplanation}`
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
return parsed.toISO();
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
class PluginTaskSchedulerJanitor {
|
|
614
|
-
knex;
|
|
615
|
-
waitBetweenRuns;
|
|
616
|
-
logger;
|
|
617
|
-
constructor(options) {
|
|
618
|
-
this.knex = options.knex;
|
|
619
|
-
this.waitBetweenRuns = options.waitBetweenRuns;
|
|
620
|
-
this.logger = options.logger;
|
|
621
|
-
}
|
|
622
|
-
async start(abortSignal) {
|
|
623
|
-
while (!abortSignal?.aborted) {
|
|
624
|
-
try {
|
|
625
|
-
await this.runOnce();
|
|
626
|
-
} catch (e) {
|
|
627
|
-
this.logger.warn(`Error while performing janitorial tasks, ${e}`);
|
|
628
|
-
}
|
|
629
|
-
await sleep(this.waitBetweenRuns, abortSignal);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
async runOnce() {
|
|
633
|
-
const dbNull = this.knex.raw("null");
|
|
634
|
-
const configClient = this.knex.client.config.client;
|
|
635
|
-
let tasks;
|
|
636
|
-
if (configClient.includes("sqlite3") || configClient.includes("mysql")) {
|
|
637
|
-
tasks = await this.knex(DB_TASKS_TABLE).select("id").where("current_run_expires_at", "<", this.knex.fn.now());
|
|
638
|
-
await this.knex(DB_TASKS_TABLE).whereIn(
|
|
639
|
-
"id",
|
|
640
|
-
tasks.map((t) => t.id)
|
|
641
|
-
).update({
|
|
642
|
-
current_run_ticket: dbNull,
|
|
643
|
-
current_run_started_at: dbNull,
|
|
644
|
-
current_run_expires_at: dbNull
|
|
645
|
-
});
|
|
646
|
-
} else {
|
|
647
|
-
tasks = await this.knex(DB_TASKS_TABLE).where("current_run_expires_at", "<", this.knex.fn.now()).update({
|
|
648
|
-
current_run_ticket: dbNull,
|
|
649
|
-
current_run_started_at: dbNull,
|
|
650
|
-
current_run_expires_at: dbNull
|
|
651
|
-
}).returning(["id"]);
|
|
652
|
-
}
|
|
653
|
-
if (typeof tasks === "number") {
|
|
654
|
-
if (tasks > 0) {
|
|
655
|
-
this.logger.warn(`${tasks} tasks timed out and were lost`);
|
|
656
|
-
}
|
|
657
|
-
} else {
|
|
658
|
-
for (const { id } of tasks) {
|
|
659
|
-
this.logger.warn(`Task timed out and was lost: ${id}`);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
class DefaultSchedulerService {
|
|
666
|
-
static create(options) {
|
|
667
|
-
const databaseFactory = lodash.once(async () => {
|
|
668
|
-
const knex = await options.database.getClient();
|
|
669
|
-
if (!options.database.migrations?.skip) {
|
|
670
|
-
await migrateBackendTasks(knex);
|
|
671
|
-
}
|
|
672
|
-
if (process.env.NODE_ENV !== "test") {
|
|
673
|
-
const janitor = new PluginTaskSchedulerJanitor({
|
|
674
|
-
knex,
|
|
675
|
-
waitBetweenRuns: luxon.Duration.fromObject({ minutes: 1 }),
|
|
676
|
-
logger: options.logger
|
|
677
|
-
});
|
|
678
|
-
janitor.start();
|
|
679
|
-
}
|
|
680
|
-
return knex;
|
|
681
|
-
});
|
|
682
|
-
return new PluginTaskSchedulerImpl(databaseFactory, options.logger);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
const schedulerServiceFactory = backendPluginApi.createServiceFactory({
|
|
687
|
-
service: backendPluginApi.coreServices.scheduler,
|
|
688
|
-
deps: {
|
|
689
|
-
database: backendPluginApi.coreServices.database,
|
|
690
|
-
logger: backendPluginApi.coreServices.logger
|
|
691
|
-
},
|
|
692
|
-
async factory({ database, logger }) {
|
|
693
|
-
return DefaultSchedulerService.create({ database, logger });
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
exports.DefaultSchedulerService = DefaultSchedulerService;
|
|
698
|
-
exports.schedulerServiceFactory = schedulerServiceFactory;
|
|
8
|
+
exports.DefaultSchedulerService = DefaultSchedulerService.DefaultSchedulerService;
|
|
9
|
+
exports.schedulerServiceFactory = schedulerServiceFactory.schedulerServiceFactory;
|
|
699
10
|
//# sourceMappingURL=scheduler.cjs.js.map
|