Skip to content

Commit 2abf833

Browse files
authored
Merge branch 'main' into client-subscribe
2 parents 39d99dc + c7ab601 commit 2abf833

30 files changed

+863
-212
lines changed

.devcontainer/devcontainer.json

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"name": "Swift",
3+
"dockerComposeFile": "docker-compose-cluster.yml",
4+
"service": "app",
5+
"workspaceFolder": "/workspace",
6+
"features": {
7+
"ghcr.io/devcontainers/features/common-utils:2": {
8+
"installZsh": "false",
9+
"username": "ubuntu",
10+
"upgradePackages": "false"
11+
},
12+
"ghcr.io/devcontainers/features/git:1": {
13+
"version": "os-provided",
14+
"ppa": "false"
15+
},
16+
"ghcr.io/swift-server-community/swift-devcontainer-features/jemalloc:1": { }
17+
},
18+
// Configure tool-specific properties.
19+
"customizations": {
20+
// Configure properties specific to VS Code.
21+
"vscode": {
22+
// Set *default* container specific settings.json values on container create.
23+
"settings": {
24+
"lldb.library": "/usr/lib/liblldb.so",
25+
"swift.path": ""
26+
},
27+
// Add the IDs of extensions you want installed when the container is created.
28+
"extensions": [
29+
"swiftlang.swift-vscode"
30+
]
31+
}
32+
},
33+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
34+
// "forwardPorts": [],
35+
36+
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
37+
"remoteUser": "ubuntu"
38+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
version: '3.8'
2+
3+
services:
4+
5+
# Here we have six Valkey containers with Cluster mode enabled,
6+
# three of them will work as primary nodes and each one of
7+
# will have a replica, so in case of failures, the replica becomes the primary.
8+
# They are configured by the `cluster_initiator` container.
9+
10+
# To make Docker compatible with Valkey Cluster, you need to use Docker's host
11+
# networking mode. Please see the --net=host option in the Docker documentation
12+
# for more information.
13+
app:
14+
image: swift:6.1
15+
network_mode: "host"
16+
volumes:
17+
- ..:/workspace
18+
depends_on:
19+
- cluster_initiator
20+
- valkey
21+
cap_add:
22+
- SYS_PTRACE
23+
security_opt:
24+
- seccomp=unconfined
25+
environment:
26+
- VALKEY_NODE1_HOSTNAME=localhost
27+
- VALKEY_NODE1_PORT=36001
28+
command: sleep infinity
29+
30+
valkey:
31+
image: 'valkey/valkey:latest'
32+
network_mode: "host"
33+
command: valkey-server --port 6379
34+
35+
valkey_cluster_1:
36+
image: 'valkey/valkey:latest'
37+
network_mode: "host"
38+
command: valkey-server --port 36001 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
39+
40+
valkey_cluster_2:
41+
image: 'valkey/valkey:latest'
42+
network_mode: "host"
43+
command: valkey-server --port 36002 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
44+
45+
valkey_cluster_3:
46+
image: 'valkey/valkey:latest'
47+
network_mode: "host"
48+
command: valkey-server --port 36003 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
49+
50+
valkey_cluster_4:
51+
image: 'valkey/valkey:latest'
52+
network_mode: "host"
53+
command: valkey-server --port 36004 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
54+
55+
valkey_cluster_5:
56+
image: 'valkey/valkey:latest'
57+
network_mode: "host"
58+
command: valkey-server --port 36005 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
59+
60+
valkey_cluster_6:
61+
image: 'valkey/valkey:latest'
62+
network_mode: "host"
63+
command: valkey-server --port 36006 --cluster-enabled yes --cluster-config-file nodes.conf --cluster-node-timeout 5000 --appendonly yes
64+
65+
# Ephemeral container to create the valkey cluster connections.
66+
# Once the setup is done, this container shuts down
67+
# and the cluster can be used by the service app container
68+
cluster_initiator:
69+
image: 'valkey/valkey:latest'
70+
network_mode: "host"
71+
container_name: cluster_initiator
72+
command: valkey-cli --cluster create localhost:36001 localhost:36002 localhost:36003 localhost:36004 localhost:36005 localhost:36006 --cluster-replicas 1 --cluster-yes
73+
tty: true
74+
depends_on:
75+
- valkey_cluster_1
76+
- valkey_cluster_2
77+
- valkey_cluster_3
78+
- valkey_cluster_4
79+
- valkey_cluster_5
80+
- valkey_cluster_6
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
version: '3.8'
2+
3+
services:
4+
5+
# Here we have four Valkey containers. A primary, two replicas replicating from
6+
# the primary and an additinal replica replicating from one of the replicas
7+
8+
# To make Docker compatible with Valkey replicas, you need to use Docker's host
9+
# networking mode. Please see the --net=host option in the Docker documentation
10+
# for more information.
11+
app:
12+
image: swift:6.1
13+
network_mode: "host"
14+
volumes:
15+
- ..:/workspace
16+
depends_on:
17+
- valkey
18+
- valkey_replica_1
19+
- valkey_replica_2
20+
- valkey_replica_3
21+
cap_add:
22+
- SYS_PTRACE
23+
security_opt:
24+
- seccomp=unconfined
25+
command: sleep infinity
26+
27+
valkey:
28+
image: 'valkey/valkey:latest'
29+
network_mode: "host"
30+
command: valkey-server --port 6379
31+
32+
valkey_replica_1:
33+
image: 'valkey/valkey:latest'
34+
network_mode: "host"
35+
depends_on:
36+
- valkey
37+
command: valkey-server --port 36001 --replicaof 127.0.0.1 6379
38+
39+
valkey_replica_2:
40+
image: 'valkey/valkey:latest'
41+
network_mode: "host"
42+
depends_on:
43+
- valkey
44+
command: valkey-server --port 36002 --replicaof 127.0.0.1 6379
45+
46+
valkey_replica_3:
47+
image: 'valkey/valkey:latest'
48+
network_mode: "host"
49+
depends_on:
50+
- valkey_replica_2
51+
command: valkey-server --port 36003 --replicaof 127.0.0.1 36002

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,5 @@ DerivedData/
99
.vscode
1010
Package.resolved
1111
.benchmarkBaselines/
12-
.devcontainer
1312
.swift-version
1413
.docc-build
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the valkey-swift open source project
4+
//
5+
// Copyright (c) 2025 the valkey-swift 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 valkey-swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Benchmark
16+
import Foundation
17+
import NIOCore
18+
import NIOPosix
19+
import Valkey
20+
21+
let defaultMetrics: [BenchmarkMetric] =
22+
// There is no point comparing wallClock, cpuTotal or throughput on CI as they are too inconsistent
23+
ProcessInfo.processInfo.environment["CI"] != nil
24+
? [
25+
.instructions,
26+
.mallocCountTotal,
27+
]
28+
: [
29+
.wallClock,
30+
.cpuTotal,
31+
.instructions,
32+
.mallocCountTotal,
33+
.throughput,
34+
]
35+
36+
func makeLocalServer() async throws -> Channel {
37+
struct GetHandler: BenchmarkCommandHandler {
38+
static let expectedCommand = RESPToken.Value.bulkString(ByteBuffer(string: "GET"))
39+
static let response = ByteBuffer(string: "$3\r\nBar\r\n")
40+
func handle(command: RESPToken.Value, parameters: RESPToken.Array.Iterator, write: (ByteBuffer) -> Void) {
41+
switch command {
42+
case Self.expectedCommand:
43+
write(Self.response)
44+
case .bulkString(ByteBuffer(string: "PING")):
45+
write(ByteBuffer(string: "$4\r\nPONG\r\n"))
46+
case .bulkString(let string):
47+
fatalError("Unexpected command: \(String(buffer: string))")
48+
default:
49+
fatalError("Unexpected value: \(command)")
50+
}
51+
}
52+
}
53+
return try await ServerBootstrap(group: NIOSingletons.posixEventLoopGroup)
54+
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
55+
.childChannelInitializer { channel in
56+
do {
57+
try channel.pipeline.syncOperations.addHandler(
58+
ValkeyServerChannelHandler(commandHandler: GetHandler())
59+
)
60+
return channel.eventLoop.makeSucceededVoidFuture()
61+
} catch {
62+
return channel.eventLoop.makeFailedFuture(error)
63+
}
64+
}
65+
.bind(host: "127.0.0.1", port: 0)
66+
.get()
67+
}
68+
69+
protocol BenchmarkCommandHandler {
70+
func handle(command: RESPToken.Value, parameters: RESPToken.Array.Iterator, write: (ByteBuffer) -> Void)
71+
}
72+
73+
final class ValkeyServerChannelHandler<Handler: BenchmarkCommandHandler>: ChannelInboundHandler {
74+
75+
typealias InboundIn = ByteBuffer
76+
typealias OutboundOut = ByteBuffer
77+
78+
private var decoder = NIOSingleStepByteToMessageProcessor(RESPTokenDecoder())
79+
private let helloCommand = RESPToken.Value.bulkString(ByteBuffer(string: "HELLO"))
80+
private let helloResponse = ByteBuffer(string: "%1\r\n+server\r\n+fake\r\n")
81+
private let commandHandler: Handler
82+
83+
init(commandHandler: Handler) {
84+
self.commandHandler = commandHandler
85+
}
86+
87+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
88+
try! self.decoder.process(buffer: self.unwrapInboundIn(data)) { token in
89+
self.handleToken(context: context, token: token)
90+
}
91+
}
92+
93+
func handleToken(context: ChannelHandlerContext, token: RESPToken) {
94+
guard case .array(let array) = token.value else {
95+
fatalError()
96+
}
97+
var iterator = array.makeIterator()
98+
guard let command = iterator.next()?.value else {
99+
fatalError()
100+
}
101+
switch command {
102+
case helloCommand:
103+
context.writeAndFlush(self.wrapOutboundOut(helloResponse), promise: nil)
104+
105+
default:
106+
commandHandler.handle(command: command, parameters: iterator) {
107+
context.writeAndFlush(self.wrapOutboundOut($0), promise: nil)
108+
}
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)