2
2
//
3
3
// This source file is part of the Hummingbird server framework project
4
4
//
5
- // Copyright (c) 2024 the Hummingbird authors
5
+ // Copyright (c) 2024-2025 the Hummingbird authors
6
6
// Licensed under Apache License v2.0
7
7
//
8
8
// See LICENSE.txt for license information
@@ -173,10 +173,9 @@ public final class PostgresJobQueue: JobQueueDriver {
173
173
/// Push Job onto queue
174
174
/// - Returns: Identifier of queued job
175
175
@discardableResult public func push< Parameters> ( _ jobRequest: JobRequest < Parameters > , options: JobOptions ) async throws -> JobID {
176
- let buffer = try self . jobRegistry. encode ( jobRequest: jobRequest)
177
176
let jobID = JobID ( )
178
177
try await self . client. withTransaction ( logger: self . logger) { connection in
179
- try await self . add ( jobID: jobID, jobBuffer : buffer , connection: connection)
178
+ try await self . add ( jobID: jobID, jobRequest : jobRequest , connection: connection)
180
179
try await self . addToQueue ( jobID: jobID, connection: connection, delayUntil: options. delayUntil)
181
180
}
182
181
return jobID
@@ -233,10 +232,15 @@ public final class PostgresJobQueue: JobQueueDriver {
233
232
}
234
233
235
234
func popFirst( ) async throws -> JobQueueResult < JobID > ? {
235
+ enum PopFirstResult {
236
+ case nothing
237
+ case result( Result < PostgresRow , Error > , jobID: JobID )
238
+ }
236
239
do {
237
240
// The withTransaction closure returns a Result<(ByteBuffer, JobID)?, Error> because
238
241
// we want to be able to exit the closure without cancelling the transaction
239
- let result = try await self . client. withTransaction ( logger: self . logger) { connection -> Result < ( ByteBuffer , JobID ) ? , Error > in
242
+ let popFirstResult = try await self . client. withTransaction ( logger: self . logger) {
243
+ connection -> PopFirstResult in
240
244
try Task . checkCancellation ( )
241
245
242
246
let stream = try await connection. query (
@@ -259,7 +263,7 @@ public final class PostgresJobQueue: JobQueueDriver {
259
263
)
260
264
// return nil if nothing in queue
261
265
guard let jobID = try await stream. decode ( UUID . self, context: . default) . first ( where: { _ in true } ) else {
262
- return Result . success ( nil )
266
+ return . nothing
263
267
}
264
268
// set job status to processing
265
269
try await self . setStatus ( jobID: jobID, status: . processing, connection: connection)
@@ -269,36 +273,41 @@ public final class PostgresJobQueue: JobQueueDriver {
269
273
" SELECT job FROM _hb_pg_jobs WHERE id = \( jobID) " ,
270
274
logger: self . logger
271
275
)
272
- guard let buffer = try await stream2. decode ( ByteBuffer . self , context : . default ) . first ( where: { _ in true } ) else {
273
- logger. error (
276
+ guard let row = try await stream2. first ( where: { _ in true } ) else {
277
+ logger. info (
274
278
" Failed to find job with id " ,
275
279
metadata: [
276
280
" JobID " : " \( jobID) "
277
281
]
278
282
)
279
- // if failed to find the job in the job table return nil
280
- return . success ( nil )
283
+ // if failed to find the job in the job table return error
284
+ return . result ( . failure ( JobQueueError ( code : . unrecognisedJobId , jobName : nil ) ) , jobID : jobID )
281
285
}
282
- return . success( ( buffer, jobID) )
283
-
286
+ return . result( . success( row) , jobID: jobID)
284
287
}
285
- guard let ( buffer, jobID) = try result. get ( ) else { return nil }
286
- do {
287
- let jobInstance = try self . jobRegistry. decode ( buffer)
288
- return JobQueueResult ( id: jobID, result: . success( jobInstance) )
289
- } catch let error as JobQueueError {
290
- return JobQueueResult ( id: jobID, result: . failure( error) )
288
+
289
+ switch popFirstResult {
290
+ case . nothing:
291
+ return nil
292
+ case . result( let result, let jobID) :
293
+ do {
294
+ let row = try result. get ( )
295
+ let jobInstance = try row. decode ( AnyDecodableJob . self, context: . withJobRegistry( self . jobRegistry) ) . job
296
+ return JobQueueResult ( id: jobID, result: . success( jobInstance) )
297
+ } catch let error as JobQueueError {
298
+ return JobQueueResult ( id: jobID, result: . failure( error) )
299
+ }
291
300
}
292
301
} catch let error as PSQLError {
293
- logger. error (
302
+ logger. info (
294
303
" Failed to get job from queue " ,
295
304
metadata: [
296
305
" error " : " \( String ( reflecting: error) ) "
297
306
]
298
307
)
299
308
throw error
300
309
} catch let error as JobQueueError {
301
- logger. error (
310
+ logger. info (
302
311
" Job failed " ,
303
312
metadata: [
304
313
" error " : " \( String ( reflecting: error) ) "
@@ -308,11 +317,11 @@ public final class PostgresJobQueue: JobQueueDriver {
308
317
}
309
318
}
310
319
311
- func add( jobID: JobID , jobBuffer : ByteBuffer , connection: PostgresConnection ) async throws {
320
+ func add< Parameters > ( jobID: JobID , jobRequest : JobRequest < Parameters > , connection: PostgresConnection ) async throws {
312
321
try await connection. query (
313
322
"""
314
323
INSERT INTO _hb_pg_jobs (id, job, status)
315
- VALUES ( \( jobID) , \( jobBuffer ) , \( Status . pending) )
324
+ VALUES ( \( jobID) , \( jobRequest ) , \( Status . pending) )
316
325
""" ,
317
326
logger: self . logger
318
327
)
@@ -443,3 +452,12 @@ extension JobQueueDriver where Self == PostgresJobQueue {
443
452
await Self ( client: client, migrations: migrations, configuration: configuration, logger: logger)
444
453
}
445
454
}
455
+
456
+ extension PostgresDecodingContext where JSONDecoder == Foundation . JSONDecoder {
457
+ /// A ``PostgresDecodingContext`` that uses a Foundation `JSONDecoder` with job registry attached as userInfo.
458
+ public static func withJobRegistry( _ jobRegistry: JobRegistry ) -> PostgresDecodingContext {
459
+ let jsonDecoder = JSONDecoder ( )
460
+ jsonDecoder. userInfo [ . _jobConfiguration] = jobRegistry
461
+ return PostgresDecodingContext ( jsonDecoder: jsonDecoder)
462
+ }
463
+ }
0 commit comments