@@ -45,12 +45,6 @@ import PostgresNIO
45
45
public final class PostgresJobQueue : JobQueueDriver , CancellableJobQueue , ResumableJobQueue {
46
46
47
47
public typealias JobID = UUID
48
- /// what to do with failed/processing jobs from last time queue was handled
49
- public enum JobCleanup : Sendable {
50
- case doNothing
51
- case rerun
52
- case remove
53
- }
54
48
55
49
/// Job priority from lowest to highest
56
50
public struct JobPriority : Equatable , Sendable {
@@ -124,6 +118,7 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
124
118
case failed = 2
125
119
case cancelled = 3
126
120
case paused = 4
121
+ case completed = 5
127
122
}
128
123
129
124
/// Queue configuration
@@ -132,17 +127,21 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
132
127
let pollTime : Duration
133
128
/// Which Queue to push jobs into
134
129
let queueName : String
130
+ /// Retention policy for jobs
131
+ let retentionPolicy : RetentionPolicy
135
132
136
133
/// Initialize configuration
137
134
/// - Parameters
138
135
/// - pollTime: Queue poll time to wait if queue empties
139
136
/// - queueName: Name of queue we are handing
140
137
public init (
141
138
pollTime: Duration = . milliseconds( 100 ) ,
142
- queueName: String = " default "
139
+ queueName: String = " default " ,
140
+ retentionPolicy: RetentionPolicy = . init( )
143
141
) {
144
142
self . pollTime = pollTime
145
143
self . queueName = queueName
144
+ self . retentionPolicy = retentionPolicy
146
145
}
147
146
}
148
147
@@ -152,7 +151,6 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
152
151
public let configuration : Configuration
153
152
/// Logger used by queue
154
153
public let logger : Logger
155
-
156
154
let migrations : DatabaseMigrations
157
155
let isStopped : NIOLockedValueBox < Bool >
158
156
@@ -165,6 +163,7 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
165
163
self . isStopped = . init( false )
166
164
self . migrations = migrations
167
165
await migrations. add ( CreateSwiftJobsMigrations ( ) , skipDuplicates: true )
166
+ self . registerCleanupJob ( )
168
167
}
169
168
170
169
public func onInit( ) async throws {
@@ -184,7 +183,11 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
184
183
public func cancel( jobID: JobID ) async throws {
185
184
try await self . client. withTransaction ( logger: logger) { connection in
186
185
try await deleteFromQueue ( jobID: jobID, connection: connection)
187
- try await delete ( jobID: jobID)
186
+ if configuration. retentionPolicy. cancelled == . doNotRetain {
187
+ try await delete ( jobID: jobID)
188
+ } else {
189
+ try await setStatus ( jobID: jobID, status: . cancelled, connection: connection)
190
+ }
188
191
}
189
192
}
190
193
@@ -223,53 +226,6 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
223
226
}
224
227
}
225
228
226
- /// Cleanup job queues
227
- ///
228
- /// This function is used to re-run or delete jobs in a certain state. Failed jobs can be
229
- /// pushed back into the pending queue to be re-run or removed. When called at startup in
230
- /// theory no job should be set to processing, or set to pending but not in the queue. but if
231
- /// your job server crashes these states are possible, so we also provide options to re-queue
232
- /// these jobs so they are run again.
233
- ///
234
- /// The job queue needs to be running when you call cleanup. You can call `cleanup` with
235
- /// `failedJobs`` set to whatever you like at any point to re-queue failed jobs. Moving processing
236
- /// or pending jobs should only be done if you are certain there is nothing else processing
237
- /// the job queue.
238
- ///
239
- /// - Parameters:
240
- /// - failedJobs: What to do with jobs tagged as failed
241
- /// - processingJobs: What to do with jobs tagged as processing
242
- /// - pendingJobs: What to do with jobs tagged as pending
243
- /// - Throws:
244
- public func cleanup(
245
- failedJobs: JobCleanup = . doNothing,
246
- processingJobs: JobCleanup = . doNothing,
247
- pendingJobs: JobCleanup = . doNothing
248
- ) async throws {
249
- do {
250
- /// wait for migrations to complete before running job queue cleanup
251
- try await self . migrations. waitUntilCompleted ( )
252
- _ = try await self . client. withConnection { connection in
253
- self . logger. info ( " Update Jobs " )
254
- try await self . updateJobsOnInit ( withStatus: . pending, onInit: pendingJobs, connection: connection)
255
- try await self . updateJobsOnInit (
256
- withStatus: . processing,
257
- onInit: processingJobs,
258
- connection: connection
259
- )
260
- try await self . updateJobsOnInit ( withStatus: . failed, onInit: failedJobs, connection: connection)
261
- }
262
- } catch let error as PSQLError {
263
- logger. error (
264
- " JobQueue initialization failed " ,
265
- metadata: [
266
- " Error " : " \( String ( reflecting: error) ) "
267
- ]
268
- )
269
- throw error
270
- }
271
- }
272
-
273
229
/// Register job
274
230
/// - Parameters:
275
231
/// - job: Job Definition
@@ -290,15 +246,15 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
290
246
291
247
/// Retry an existing Job
292
248
/// - Parameters
293
- /// - id : Job instance ID
249
+ /// - jobID : Job instance ID
294
250
/// - jobRequest: Job Request
295
251
/// - options: Job retry options
296
- public func retry< Parameters> ( _ id : JobID , jobRequest: JobRequest < Parameters > , options: JobRetryOptions ) async throws {
252
+ public func retry< Parameters> ( _ jobID : JobID , jobRequest: JobRequest < Parameters > , options: JobRetryOptions ) async throws {
297
253
let buffer = try self . jobRegistry. encode ( jobRequest: jobRequest)
298
254
try await self . client. withTransaction ( logger: self . logger) { connection in
299
- try await self . updateJob ( id : id , buffer: buffer, connection: connection)
255
+ try await self . updateJob ( jobID : jobID , buffer: buffer, connection: connection)
300
256
try await self . addToQueue (
301
- jobID: id ,
257
+ jobID: jobID ,
302
258
queueName: configuration. queueName,
303
259
options: . init( delayUntil: options. delayUntil) ,
304
260
connection: connection
@@ -308,12 +264,20 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
308
264
309
265
/// This is called to say job has finished processing and it can be deleted
310
266
public func finished( jobID: JobID ) async throws {
311
- try await self . delete ( jobID: jobID)
267
+ if configuration. retentionPolicy. completed == . doNotRetain {
268
+ try await self . delete ( jobID: jobID)
269
+ } else {
270
+ try await self . setStatus ( jobID: jobID, status: . completed)
271
+ }
312
272
}
313
273
314
274
/// This is called to say job has failed to run and should be put aside
315
275
public func failed( jobID: JobID , error: Error ) async throws {
316
- try await self . setStatus ( jobID: jobID, status: . failed)
276
+ if configuration. retentionPolicy. failed == . doNotRetain {
277
+ try await self . delete ( jobID: jobID)
278
+ } else {
279
+ try await self . setStatus ( jobID: jobID, status: . failed)
280
+ }
317
281
}
318
282
319
283
/// stop serving jobs
@@ -451,15 +415,15 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
451
415
logger: self . logger
452
416
)
453
417
}
454
- // TODO: maybe add a new column colum for attempt so far after PR https://github.com/hummingbird-project/swift-jobs/pull/63 is merged?
455
- func updateJob( id : JobID , buffer: ByteBuffer , connection: PostgresConnection ) async throws {
418
+
419
+ func updateJob( jobID : JobID , buffer: ByteBuffer , connection: PostgresConnection ) async throws {
456
420
try await connection. query (
457
421
"""
458
422
UPDATE swift_jobs.jobs
459
423
SET job = \( buffer) ,
460
424
last_modified = \( Date . now) ,
461
425
status = \( Status . failed)
462
- WHERE id = \( id ) AND queue_name = \( configuration. queueName)
426
+ WHERE id = \( jobID ) AND queue_name = \( configuration. queueName)
463
427
""" ,
464
428
logger: self . logger
465
429
)
@@ -538,27 +502,21 @@ public final class PostgresJobQueue: JobQueueDriver, CancellableJobQueue, Resuma
538
502
return jobs
539
503
}
540
504
541
- func updateJobsOnInit( withStatus status: Status , onInit: JobCleanup , connection: PostgresConnection ) async throws {
542
- switch onInit {
543
- case . remove:
544
- try await connection. query (
545
- """
546
- DELETE FROM swift_jobs.jobs
547
- WHERE status = \( status) AND queue_name = \( configuration. queueName)
548
- """ ,
549
- logger: self . logger
550
- )
551
-
552
- case . rerun:
553
- let jobs = try await getJobs ( withStatus: status)
554
- self . logger. info ( " Moving \( jobs. count) jobs with status: \( status) to job queue " )
555
- for jobID in jobs {
556
- try await self . addToQueue ( jobID: jobID, queueName: configuration. queueName, options: . init( ) , connection: connection)
557
- }
558
-
559
- case . doNothing:
560
- break
505
+ func getJobs( withStatus status: Status , connection: PostgresConnection ) async throws -> [ JobID ] {
506
+ let stream = try await connection. query (
507
+ """
508
+ SELECT
509
+ id
510
+ FROM swift_jobs.jobs
511
+ WHERE status = \( status) AND queue_name = \( configuration. queueName)
512
+ """ ,
513
+ logger: self . logger
514
+ )
515
+ var jobs : [ JobID ] = [ ]
516
+ for try await id in stream. decode ( JobID . self, context: . default) {
517
+ jobs. append ( id)
561
518
}
519
+ return jobs
562
520
}
563
521
564
522
let jobRegistry : JobRegistry
0 commit comments