@koralabs/kora-labs-common 6.15.0 → 6.15.2
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/cronLock/cqlCronLock.d.ts +18 -1
- package/cronLock/cqlCronLock.js +49 -12
- package/package.json +1 -1
- package/txBuild/blockfrost.d.ts +5 -0
- package/txBuild/blockfrost.js +4 -0
|
@@ -38,6 +38,11 @@ export declare const resolveMode: (offline: string[], topology: CronLockTopology
|
|
|
38
38
|
export declare const isPeerUnavailableError: (error: unknown) => boolean;
|
|
39
39
|
/** A write timeout leaves an LWT's outcome unknown: the proposal may or may not have been applied. */
|
|
40
40
|
export declare const isWriteTimeoutError: (error: unknown) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* An LWT whose outcome is unknown: the coordinator reported a write timeout, or it never replied within
|
|
43
|
+
* the driver's readTimeout ("The host … did not reply before timeout") — either way it may have committed.
|
|
44
|
+
*/
|
|
45
|
+
export declare const isUnknownOutcomeError: (error: unknown) => boolean;
|
|
41
46
|
/**
|
|
42
47
|
* The directly testable LWT operation. Each invocation owns a unique token, so
|
|
43
48
|
* concurrent ticks on one node cannot both treat the node identity as ownership.
|
|
@@ -46,6 +51,18 @@ export declare const acquireWithExecutor: (executor: CqlExecutor, name: string,
|
|
|
46
51
|
export declare const acquire: (name: string, offline?: string | null, options?: {
|
|
47
52
|
leaseMs?: number;
|
|
48
53
|
}) => Promise<AcquireResult>;
|
|
49
|
-
|
|
54
|
+
export type ReleaseResult = {
|
|
55
|
+
status: 'released';
|
|
56
|
+
} | {
|
|
57
|
+
status: 'unknown';
|
|
58
|
+
message: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Release only the exact acquisition token owned by this invocation. The conditional DELETE is an LWT
|
|
62
|
+
* too: when it fails its outcome is unknown, and a surviving row would idle the job on every box for
|
|
63
|
+
* the whole lease. A serial read completes any in-flight Paxos round and tells whether the row is still
|
|
64
|
+
* ours; if it is, the (token-conditional) delete is issued once more.
|
|
65
|
+
*/
|
|
66
|
+
export declare const releaseWithExecutor: (executor: CqlExecutor, name: string, token: string, offline: string | null | undefined, topology: CronLockTopology) => Promise<ReleaseResult>;
|
|
50
67
|
export declare const release: (name: string, token: string, offline?: string | null) => Promise<void>;
|
|
51
68
|
export {};
|
package/cronLock/cqlCronLock.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.release = exports.acquire = exports.acquireWithExecutor = exports.isWriteTimeoutError = exports.isPeerUnavailableError = exports.resolveMode = exports.parseOffline = exports.leaseMs = exports.resolveTopology = void 0;
|
|
3
|
+
exports.release = exports.releaseWithExecutor = exports.acquire = exports.acquireWithExecutor = exports.isUnknownOutcomeError = exports.isWriteTimeoutError = exports.isPeerUnavailableError = exports.resolveMode = exports.parseOffline = exports.leaseMs = exports.resolveTopology = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Cross-datacenter cron mutex for the self-hosted, multi-DC Scylla topology.
|
|
6
6
|
* (Moved from minting.handle.me src/helpers/cronLock/cqlCronLock.ts @ 6ecb9c3 so every box cron
|
|
@@ -117,6 +117,12 @@ exports.isPeerUnavailableError = isPeerUnavailableError;
|
|
|
117
117
|
/** A write timeout leaves an LWT's outcome unknown: the proposal may or may not have been applied. */
|
|
118
118
|
const isWriteTimeoutError = (error) => error instanceof cassandra_driver_1.errors.ResponseError && error.code === cassandra_driver_1.types.responseErrorCodes.writeTimeout;
|
|
119
119
|
exports.isWriteTimeoutError = isWriteTimeoutError;
|
|
120
|
+
/**
|
|
121
|
+
* An LWT whose outcome is unknown: the coordinator reported a write timeout, or it never replied within
|
|
122
|
+
* the driver's readTimeout ("The host … did not reply before timeout") — either way it may have committed.
|
|
123
|
+
*/
|
|
124
|
+
const isUnknownOutcomeError = (error) => (0, exports.isWriteTimeoutError)(error) || error instanceof cassandra_driver_1.errors.OperationTimedOutError;
|
|
125
|
+
exports.isUnknownOutcomeError = isUnknownOutcomeError;
|
|
120
126
|
/**
|
|
121
127
|
* The directly testable LWT operation. Each invocation owns a unique token, so
|
|
122
128
|
* concurrent ticks on one node cannot both treat the node identity as ownership.
|
|
@@ -144,7 +150,7 @@ const acquireWithExecutor = async (executor, name, offline, topology, token = cr
|
|
|
144
150
|
if (mode === 'serial' && (0, exports.isPeerUnavailableError)(error)) {
|
|
145
151
|
return { status: 'peerUnavailable', token, message };
|
|
146
152
|
}
|
|
147
|
-
if ((0, exports.
|
|
153
|
+
if ((0, exports.isUnknownOutcomeError)(error)) {
|
|
148
154
|
// The LWT timed out: it may have been applied WITH OUR TOKEN, in which case nobody (not even
|
|
149
155
|
// us) could run until the lease expired. A read at the same serial consistency completes any
|
|
150
156
|
// in-flight Paxos round, so it tells us the real owner.
|
|
@@ -217,22 +223,53 @@ const acquire = async (name, offline, options = {}) => {
|
|
|
217
223
|
}
|
|
218
224
|
};
|
|
219
225
|
exports.acquire = acquire;
|
|
220
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* Release only the exact acquisition token owned by this invocation. The conditional DELETE is an LWT
|
|
228
|
+
* too: when it fails its outcome is unknown, and a surviving row would idle the job on every box for
|
|
229
|
+
* the whole lease. A serial read completes any in-flight Paxos round and tells whether the row is still
|
|
230
|
+
* ours; if it is, the (token-conditional) delete is issued once more.
|
|
231
|
+
*/
|
|
232
|
+
const releaseWithExecutor = async (executor, name, token, offline, topology) => {
|
|
233
|
+
const mode = (0, exports.resolveMode)((0, exports.parseOffline)(offline), topology);
|
|
234
|
+
if (mode === 'standDown')
|
|
235
|
+
return { status: 'released' };
|
|
236
|
+
const serialConsistency = mode === 'localSerial' ? cassandra_driver_1.types.consistencies.localSerial : cassandra_driver_1.types.consistencies.serial;
|
|
237
|
+
const consistency = mode === 'localSerial' ? cassandra_driver_1.types.consistencies.localQuorum : cassandra_driver_1.types.consistencies.quorum;
|
|
238
|
+
const key = scopeName(name, topology);
|
|
239
|
+
try {
|
|
240
|
+
await executor.execute(`DELETE FROM ${KEYSPACE}.${LOCK_TABLE} WHERE name = ? IF owner = ?`, [key, token], { prepare: true, serialConsistency, consistency });
|
|
241
|
+
return { status: 'released' };
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
245
|
+
const read = await readOwner(executor, key, serialConsistency);
|
|
246
|
+
if (read.error)
|
|
247
|
+
return { status: 'unknown', message: `${message}; owner read failed (${read.error})` };
|
|
248
|
+
if (read.owner !== token)
|
|
249
|
+
return { status: 'released' };
|
|
250
|
+
return (await deleteIfOwner(executor, key, token, serialConsistency, consistency))
|
|
251
|
+
? { status: 'released' }
|
|
252
|
+
: { status: 'unknown', message: `${message}; the row is still ours and the second delete failed` };
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
exports.releaseWithExecutor = releaseWithExecutor;
|
|
221
256
|
const release = async (name, token, offline) => {
|
|
222
257
|
try {
|
|
223
258
|
const topology = (0, exports.resolveTopology)();
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
259
|
+
const result = await (0, exports.releaseWithExecutor)(getClient(topology), name, token, offline, topology);
|
|
260
|
+
if (result.status === 'unknown') {
|
|
261
|
+
logger_1.Logger.log({
|
|
262
|
+
message: `cron lock '${name}' release failed; if the row survived, '${name}' idles on every box until its lease expires: ${result.message}`,
|
|
263
|
+
event: 'cqlCronLock.release.unknown',
|
|
264
|
+
category: logger_1.LogCategory.WARN
|
|
265
|
+
});
|
|
266
|
+
}
|
|
230
267
|
}
|
|
231
268
|
catch (error) {
|
|
232
269
|
logger_1.Logger.log({
|
|
233
|
-
message: `cron lock '${name}' release
|
|
234
|
-
event: 'cqlCronLock.release.
|
|
235
|
-
category: logger_1.LogCategory.
|
|
270
|
+
message: `cron lock '${name}' release failed (TTL will reclaim): ${error.message}`,
|
|
271
|
+
event: 'cqlCronLock.release.unknown',
|
|
272
|
+
category: logger_1.LogCategory.WARN
|
|
236
273
|
});
|
|
237
274
|
}
|
|
238
275
|
};
|
package/package.json
CHANGED
package/txBuild/blockfrost.d.ts
CHANGED
|
@@ -58,6 +58,11 @@ export declare class BlockfrostTxClient {
|
|
|
58
58
|
getAddressAssetUtxos(address: string, unit: string): Promise<Cardano.Utxo[]>;
|
|
59
59
|
/** The UTxO `txHash#index`; throws `already spent` when it has been consumed. */
|
|
60
60
|
getUtxo(ref: string): Promise<Cardano.Utxo>;
|
|
61
|
+
/** A stake address's registration state; null when the chain has never seen it. */
|
|
62
|
+
getAccount(stakeAddress: string): Promise<{
|
|
63
|
+
stake_address: string;
|
|
64
|
+
active: boolean;
|
|
65
|
+
} | null>;
|
|
61
66
|
/** Node (Ogmios) evaluation of every redeemer; `additionalUtxos` supplies inputs not yet on chain. */
|
|
62
67
|
evaluateTx(cbor: string, additionalUtxos?: Cardano.Utxo[]): Promise<Map<RedeemerKey, ExUnits>>;
|
|
63
68
|
/** Submit a signed tx; returns its hash. */
|
package/txBuild/blockfrost.js
CHANGED
|
@@ -188,6 +188,10 @@ class BlockfrostTxClient {
|
|
|
188
188
|
throw new Error(`${ref} already spent by ${output.consumed_by_tx}`);
|
|
189
189
|
return this.toUtxo(output, txHash, output.address);
|
|
190
190
|
}
|
|
191
|
+
/** A stake address's registration state; null when the chain has never seen it. */
|
|
192
|
+
getAccount(stakeAddress) {
|
|
193
|
+
return this.getOrNull(`accounts/${stakeAddress}`);
|
|
194
|
+
}
|
|
191
195
|
/** Node (Ogmios) evaluation of every redeemer; `additionalUtxos` supplies inputs not yet on chain. */
|
|
192
196
|
async evaluateTx(cbor, additionalUtxos = []) {
|
|
193
197
|
var _a, _b, _c, _d, _e;
|