I'm trying to create a MockHTTPServer using NIO. Trying to make it swift 6 compatible. I'm stuck trying to figure out how to resolve Call to actor-isolated instance method 'getResponse(for:)' in a synchronous nonisolated context
which occurs in the MockHTTPHandler. I'm hoping someone will tell me it's simple. Wrapping the offending code in a Task creates a different error: "Task-isolated value of type '() async -> ()' passed as a strongly transferred parameter; later accesses could race" and I couldn't figure out which function call is racing.
fileprivate actor MockHTTPServer {
private let group: EventLoopGroup
private let channel: Channel
let port: Int
private var responses: [String: (HTTPResponseStatus, String)] = [:]
private init(group: EventLoopGroup, channel: Channel) {
self.group = group
self.channel = channel
self.port = channel.localAddress?.port ?? -1
}
static func start(group: EventLoopGroup) async throws -> MockHTTPServer {
let server = try await withCheckedThrowingContinuation { continuation in
let bootstrap = ServerBootstrap(group: group)
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
channel.pipeline.configureHTTPServerPipeline().flatMap { _ in
let handler = MockHTTPHandler()
return channel.pipeline.addHandler(handler)
}
}
bootstrap.bind(host: "localhost", port: 0).whenComplete { result in
switch result {
case .success(let channel):
let server = MockHTTPServer(group: group, channel: channel)
channel.pipeline.handler(type: MockHTTPHandler.self).whenSuccess { handler in
handler.server = server
}
continuation.resume(returning: server)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
return server
}
func stop() async throws {
try await channel.close()
}
func addResponse(for path: String, statusCode: HTTPResponseStatus, body: String) {
responses[path] = (statusCode, body)
}
func getResponse(for path: String) -> (HTTPResponseStatus, String)? {
return responses[path]
}
private final class MockHTTPHandler: ChannelInboundHandler {
typealias InboundIn = HTTPServerRequestPart
typealias OutboundOut = HTTPServerResponsePart
weak var server: MockHTTPServer?
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let reqPart = unwrapInboundIn(data)
guard case .head(let head) = reqPart else { return }
guard let server = self.server else {
respond(context: context, status: .internalServerError, body: "Server not found")
return
}
if let (status, body) = server.getResponse(for: head.uri) {
respond(context: context, status: status, body: body)
} else {
respond(context: context, status: .notFound, body: "Not Found")
}
}
private func respond(context: ChannelHandlerContext, status: HTTPResponseStatus, body: String) {
var headers = HTTPHeaders()
headers.add(name: "Content-Type", value: "application/json")
headers.add(name: "Content-Length", value: "\(body.utf8.count)")
let head = HTTPResponseHead(version: .http1_1, status: status, headers: headers)
context.write(wrapOutboundOut(.head(head)), promise: nil)
var buffer = context.channel.allocator.buffer(capacity: body.utf8.count)
buffer.writeString(body)
context.write(wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil)
context.writeAndFlush(wrapOutboundOut(.end(nil)), promise: nil)
}
}
}
You can read more about this error in this excellent blog. In your example, the compiler is raising error about property weak var server: MockHTTPServer?
as It is mutable because var
keyword which can introduce data races
.