@@ -3,6 +3,9 @@ const { default: Redlock } = require("redlock");
3
3
import { AsyncLocalStorage } from "async_hooks" ;
4
4
import { Redis } from "@internal/redis" ;
5
5
import * as redlock from "redlock" ;
6
+ import { tryCatch } from "@trigger.dev/core" ;
7
+ import { Logger } from "@trigger.dev/core/logger" ;
8
+ import { startSpan , Tracer } from "@internal/tracing" ;
6
9
7
10
interface LockContext {
8
11
resources : string ;
@@ -12,8 +15,10 @@ interface LockContext {
12
15
export class RunLocker {
13
16
private redlock : InstanceType < typeof redlock . default > ;
14
17
private asyncLocalStorage : AsyncLocalStorage < LockContext > ;
18
+ private logger : Logger ;
19
+ private tracer : Tracer ;
15
20
16
- constructor ( options : { redis : Redis } ) {
21
+ constructor ( options : { redis : Redis ; logger : Logger ; tracer : Tracer } ) {
17
22
this . redlock = new Redlock ( [ options . redis ] , {
18
23
driftFactor : 0.01 ,
19
24
retryCount : 10 ,
@@ -22,30 +27,54 @@ export class RunLocker {
22
27
automaticExtensionThreshold : 500 , // time in ms
23
28
} ) ;
24
29
this . asyncLocalStorage = new AsyncLocalStorage < LockContext > ( ) ;
30
+ this . logger = options . logger ;
31
+ this . tracer = options . tracer ;
25
32
}
26
33
27
34
/** Locks resources using RedLock. It won't lock again if we're already inside a lock with the same resources. */
28
35
async lock < T > (
36
+ name : string ,
29
37
resources : string [ ] ,
30
38
duration : number ,
31
39
routine : ( signal : redlock . RedlockAbortSignal ) => Promise < T >
32
40
) : Promise < T > {
33
41
const currentContext = this . asyncLocalStorage . getStore ( ) ;
34
42
const joinedResources = resources . sort ( ) . join ( "," ) ;
35
43
36
- if ( currentContext && currentContext . resources === joinedResources ) {
37
- // We're already inside a lock with the same resources, just run the routine
38
- return routine ( currentContext . signal ) ;
39
- }
44
+ return startSpan (
45
+ this . tracer ,
46
+ "RunLocker.lock" ,
47
+ async ( span ) => {
48
+ if ( currentContext && currentContext . resources === joinedResources ) {
49
+ span . setAttribute ( "nested" , true ) ;
50
+ // We're already inside a lock with the same resources, just run the routine
51
+ return routine ( currentContext . signal ) ;
52
+ }
40
53
41
- // Different resources or not in a lock, proceed with new lock
42
- return this . redlock . using ( resources , duration , async ( signal ) => {
43
- const newContext : LockContext = { resources : joinedResources , signal } ;
54
+ span . setAttribute ( "nested" , false ) ;
44
55
45
- return this . asyncLocalStorage . run ( newContext , async ( ) => {
46
- return routine ( signal ) ;
47
- } ) ;
48
- } ) ;
56
+ // Different resources or not in a lock, proceed with new lock
57
+ const [ error , result ] = await tryCatch (
58
+ this . redlock . using ( resources , duration , async ( signal ) => {
59
+ const newContext : LockContext = { resources : joinedResources , signal } ;
60
+
61
+ return this . asyncLocalStorage . run ( newContext , async ( ) => {
62
+ return routine ( signal ) ;
63
+ } ) ;
64
+ } )
65
+ ) ;
66
+
67
+ if ( error ) {
68
+ this . logger . error ( "[RunLocker] Error locking resources" , { error, resources, duration } ) ;
69
+ throw error ;
70
+ }
71
+
72
+ return result ;
73
+ } ,
74
+ {
75
+ attributes : { name, resources, timeout : duration } ,
76
+ }
77
+ ) ;
49
78
}
50
79
51
80
isInsideLock ( ) : boolean {
0 commit comments