-
Notifications
You must be signed in to change notification settings - Fork 31
Poll for messages using TaskExecutor
#178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
9254159
0d7b579
46a04c7
a84f1c0
5565624
caa77e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import Dispatch | ||
import Logging | ||
import NIOConcurrencyHelpers | ||
import NIOCore | ||
|
@@ -72,6 +73,9 @@ public struct KafkaConsumerMessages: Sendable, AsyncSequence { | |
public struct AsyncIterator: AsyncIteratorProtocol { | ||
private let stateMachineHolder: MachineHolder | ||
let pollInterval: Duration | ||
#if swift(>=6.0) | ||
let queue: NaiveQueueExecutor | ||
#endif | ||
|
||
private final class MachineHolder: Sendable { // only for deinit | ||
let stateMachine: LockedMachine | ||
|
@@ -88,21 +92,32 @@ public struct KafkaConsumerMessages: Sendable, AsyncSequence { | |
init(stateMachine: LockedMachine, pollInterval: Duration) { | ||
self.stateMachineHolder = .init(stateMachine: stateMachine) | ||
self.pollInterval = pollInterval | ||
#if swift(>=6.0) | ||
self.queue = NaiveQueueExecutor(DispatchQueue(label: "com.swift-server.swift-kafka.message-consumer")) | ||
#endif | ||
} | ||
|
||
public func next() async throws -> Element? { | ||
// swift-kafka-client issue: https://github.com/swift-server/swift-kafka-client/issues/165 | ||
// Currently use Task.sleep() if no new messages, should use task executor preference when implemented: | ||
// https://github.com/apple/swift-evolution/blob/main/proposals/0417-task-executor-preference.md | ||
while !Task.isCancelled { | ||
let action = self.stateMachineHolder.stateMachine.withLockedValue { $0.nextConsumerPollLoopAction() } | ||
|
||
switch action { | ||
case .poll(let client): | ||
if let message = try client.consumerPoll() { // non-blocking call | ||
// Attempt to fetch a message synchronously. Bail | ||
// immediately if no message is waiting for us. | ||
if let message = try client.consumerPoll() { | ||
return message | ||
} | ||
|
||
#if swift(>=6.0) | ||
// Wait on a separate thread for the next message. | ||
mimischi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return try await withTaskExecutorPreference(queue) { | ||
try client.consumerPoll(for: Int32(self.pollInterval.inMilliseconds)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens after the time out? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We attempt to retrieve a message for |
||
} | ||
#else | ||
// No messages. Sleep a little. | ||
try await Task.sleep(for: self.pollInterval) | ||
#endif | ||
case .suspendPollLoop: | ||
try await Task.sleep(for: self.pollInterval) // not started yet | ||
case .terminatePollLoop: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the swift-kafka-client open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the swift-kafka-client project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of swift-kafka-client project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#if swift(>=6.0) | ||
import Dispatch | ||
|
||
final class NaiveQueueExecutor: TaskExecutor { | ||
mimischi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let queue: DispatchQueue | ||
mimischi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
init(_ queue: DispatchQueue) { | ||
self.queue = queue | ||
} | ||
|
||
public func enqueue(_ _job: consuming ExecutorJob) { | ||
let job = UnownedJob(_job) | ||
queue.async { | ||
job.runSynchronously( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ktoso Should we call this |
||
on: self.asUnownedTaskExecutor() | ||
) | ||
} | ||
} | ||
|
||
@inlinable | ||
public func asUnownedTaskExecutor() -> UnownedTaskExecutor { | ||
UnownedTaskExecutor(ordinary: self) | ||
} | ||
} | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't think we need this import here
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops. Left over from a refactor. Is there a linter that can help point out unused imports?