Skip to content

Commit 91155a1

Browse files
committed
Introduce SendableOpaquePointer
[Unsafe pointers are no longer `Sendable`](swiftlang/swift#70396 (comment)) starting with Swift 5.10, while a warning in earlier versions. This PR introduces `SendableOpaquePointer` to wrap `OpaquePointer` and marks it as `@unchecked Sendable`. Resolves #159
1 parent e768c1e commit 91155a1

File tree

3 files changed

+50
-24
lines changed

3 files changed

+50
-24
lines changed

Sources/Kafka/RDKafka/RDKafkaClient.swift

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,30 +32,30 @@ public final class RDKafkaClient: Sendable {
3232
}
3333

3434
/// Handle for the C library's Kafka instance.
35-
private let kafkaHandle: OpaquePointer
35+
private let kafkaHandle: SendableOpaquePointer
3636
/// A logger.
3737
private let logger: Logger
3838

3939
/// `librdkafka`'s `rd_kafka_queue_t` that events are received on.
40-
private let queue: OpaquePointer
40+
private let queueHandle: SendableOpaquePointer
4141

4242
// Use factory method to initialize
4343
private init(
4444
type: ClientType,
45-
kafkaHandle: OpaquePointer,
45+
kafkaHandle: SendableOpaquePointer,
4646
logger: Logger
4747
) {
4848
self.kafkaHandle = kafkaHandle
4949
self.logger = logger
50-
self.queue = rd_kafka_queue_get_main(self.kafkaHandle)
50+
self.queueHandle = .init(rd_kafka_queue_get_main(self.kafkaHandle.pointer))
5151

52-
rd_kafka_set_log_queue(self.kafkaHandle, self.queue)
52+
rd_kafka_set_log_queue(self.kafkaHandle.pointer, self.queueHandle.pointer)
5353
}
5454

5555
deinit {
5656
// Loose reference to librdkafka's event queue
57-
rd_kafka_queue_destroy(self.queue)
58-
rd_kafka_destroy(kafkaHandle)
57+
rd_kafka_queue_destroy(self.queueHandle.pointer)
58+
rd_kafka_destroy(kafkaHandle.pointer)
5959
}
6060

6161
/// Factory method creating a new instance of a ``RDKafkaClient``.
@@ -90,7 +90,8 @@ public final class RDKafkaClient: Sendable {
9090
throw KafkaError.client(reason: errorString)
9191
}
9292

93-
return RDKafkaClient(type: type, kafkaHandle: handle, logger: logger)
93+
let kafkaHandle = SendableOpaquePointer(handle)
94+
return RDKafkaClient(type: type, kafkaHandle: kafkaHandle, logger: logger)
9495
}
9596

9697
/// Produce a message to the Kafka cluster.
@@ -215,7 +216,7 @@ public final class RDKafkaClient: Sendable {
215216
assert(arguments.count == size)
216217

217218
return rd_kafka_produceva(
218-
self.kafkaHandle,
219+
self.kafkaHandle.pointer,
219220
arguments,
220221
arguments.count
221222
)
@@ -307,7 +308,7 @@ public final class RDKafkaClient: Sendable {
307308
events.reserveCapacity(maxEvents)
308309

309310
for _ in 0..<maxEvents {
310-
let event = rd_kafka_queue_poll(self.queue, 0)
311+
let event = rd_kafka_queue_poll(self.queueHandle.pointer, 0)
311312
defer { rd_kafka_event_destroy(event) }
312313

313314
let rdEventType = rd_kafka_event_type(event)
@@ -446,7 +447,7 @@ public final class RDKafkaClient: Sendable {
446447
/// - Returns: A ``KafkaConsumerMessage`` or `nil` if there are no new messages.
447448
/// - Throws: A ``KafkaError`` if the received message is an error message or malformed.
448449
func consumerPoll() throws -> KafkaConsumerMessage? {
449-
guard let messagePointer = rd_kafka_consumer_poll(self.kafkaHandle, 0) else {
450+
guard let messagePointer = rd_kafka_consumer_poll(self.kafkaHandle.pointer, 0) else {
450451
// No error, there might be no more messages
451452
return nil
452453
}
@@ -469,7 +470,7 @@ public final class RDKafkaClient: Sendable {
469470
/// - Parameter topicPartitionList: Pointer to a list of topics + partition pairs.
470471
func subscribe(topicPartitionList: RDKafkaTopicPartitionList) throws {
471472
try topicPartitionList.withListPointer { pointer in
472-
let result = rd_kafka_subscribe(self.kafkaHandle, pointer)
473+
let result = rd_kafka_subscribe(self.kafkaHandle.pointer, pointer)
473474
if result != RD_KAFKA_RESP_ERR_NO_ERROR {
474475
throw KafkaError.rdKafkaError(wrapping: result)
475476
}
@@ -480,7 +481,7 @@ public final class RDKafkaClient: Sendable {
480481
/// - Parameter topicPartitionList: Pointer to a list of topics + partition pairs.
481482
func assign(topicPartitionList: RDKafkaTopicPartitionList) throws {
482483
try topicPartitionList.withListPointer { pointer in
483-
let result = rd_kafka_assign(self.kafkaHandle, pointer)
484+
let result = rd_kafka_assign(self.kafkaHandle.pointer, pointer)
484485
if result != RD_KAFKA_RESP_ERR_NO_ERROR {
485486
throw KafkaError.rdKafkaError(wrapping: result)
486487
}
@@ -517,7 +518,7 @@ public final class RDKafkaClient: Sendable {
517518

518519
let error = changesList.withListPointer { listPointer in
519520
rd_kafka_commit(
520-
self.kafkaHandle,
521+
self.kafkaHandle.pointer,
521522
listPointer,
522523
1 // async = true
523524
)
@@ -560,9 +561,9 @@ public final class RDKafkaClient: Sendable {
560561

561562
changesList.withListPointer { listPointer in
562563
rd_kafka_commit_queue(
563-
self.kafkaHandle,
564+
self.kafkaHandle.pointer,
564565
listPointer,
565-
self.queue,
566+
self.queueHandle.pointer,
566567
nil,
567568
opaquePointer
568569
)
@@ -581,7 +582,7 @@ public final class RDKafkaClient: Sendable {
581582
let queue = DispatchQueue(label: "com.swift-server.swift-kafka.flush")
582583
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
583584
queue.async {
584-
let error = rd_kafka_flush(self.kafkaHandle, timeoutMilliseconds)
585+
let error = rd_kafka_flush(self.kafkaHandle.pointer, timeoutMilliseconds)
585586
if error != RD_KAFKA_RESP_ERR_NO_ERROR {
586587
continuation.resume(throwing: KafkaError.rdKafkaError(wrapping: error))
587588
} else {
@@ -596,7 +597,7 @@ public final class RDKafkaClient: Sendable {
596597
///
597598
/// Make sure to run poll loop until ``RDKafkaClient/consumerIsClosed`` returns `true`.
598599
func consumerClose() throws {
599-
let result = rd_kafka_consumer_close_queue(self.kafkaHandle, self.queue)
600+
let result = rd_kafka_consumer_close_queue(self.kafkaHandle.pointer, self.queueHandle.pointer)
600601
let kafkaError = rd_kafka_error_code(result)
601602
if kafkaError != RD_KAFKA_RESP_ERR_NO_ERROR {
602603
throw KafkaError.rdKafkaError(wrapping: kafkaError)
@@ -605,14 +606,14 @@ public final class RDKafkaClient: Sendable {
605606

606607
/// Returns `true` if the underlying `librdkafka` consumer is closed.
607608
var isConsumerClosed: Bool {
608-
rd_kafka_consumer_closed(self.kafkaHandle) == 1
609+
rd_kafka_consumer_closed(self.kafkaHandle.pointer) == 1
609610
}
610611

611612
/// Scoped accessor that enables safe access to the pointer of the client's Kafka handle.
612613
/// - Warning: Do not escape the pointer from the closure for later use.
613614
/// - Parameter body: The closure will use the Kafka handle pointer.
614615
@discardableResult
615616
func withKafkaHandlePointer<T>(_ body: (OpaquePointer) throws -> T) rethrows -> T {
616-
try body(self.kafkaHandle)
617+
try body(self.kafkaHandle.pointer)
617618
}
618619
}

Sources/Kafka/RDKafka/RDKafkaTopicHandles.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import NIOConcurrencyHelpers
1717

1818
/// Swift class that matches topic names with their respective `rd_kafka_topic_t` handles.
1919
internal final class RDKafkaTopicHandles: Sendable {
20-
private let _internal: NIOLockedValueBox<[String: OpaquePointer]>
20+
private let _internal: NIOLockedValueBox<[String: SendableOpaquePointer]>
2121

2222
// Note: we retain the client to ensure it does not get
2323
// deinitialized before rd_kafka_topic_destroy() is invoked (required)
@@ -31,7 +31,7 @@ internal final class RDKafkaTopicHandles: Sendable {
3131
deinit {
3232
self._internal.withLockedValue { dict in
3333
for (_, topicHandle) in dict {
34-
rd_kafka_topic_destroy(topicHandle)
34+
rd_kafka_topic_destroy(topicHandle.pointer)
3535
}
3636
}
3737
}
@@ -59,7 +59,7 @@ internal final class RDKafkaTopicHandles: Sendable {
5959
) throws -> OpaquePointer {
6060
try self._internal.withLockedValue { dict in
6161
if let handle = dict[topic] {
62-
return handle
62+
return handle.pointer
6363
} else {
6464
let rdTopicConf = try RDKafkaTopicConfig.createFrom(topicConfiguration: topicConfiguration)
6565
let newHandle = self.client.withKafkaHandlePointer { kafkaHandle in
@@ -76,7 +76,7 @@ internal final class RDKafkaTopicHandles: Sendable {
7676
let error = KafkaError.rdKafkaError(wrapping: rd_kafka_last_error())
7777
throw error
7878
}
79-
dict[topic] = newHandle
79+
dict[topic] = .init(newHandle)
8080
return newHandle
8181
}
8282
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the swift-kafka-client open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the swift-kafka-client project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of swift-kafka-client project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
/// A wrapper for the `OpaquePointer` used to represent different handles from `librdkafka`.
16+
///
17+
/// This wrapper silences `Sendable` warnings for the pointer introduced in Swift 5.10, and should
18+
/// only be used for handles from `librdkafka` that are known to be thread-safe.
19+
internal struct SendableOpaquePointer: @unchecked Sendable {
20+
let pointer: OpaquePointer
21+
22+
init(_ pointer: OpaquePointer) {
23+
self.pointer = pointer
24+
}
25+
}

0 commit comments

Comments
 (0)