Skip to content

Commit 8060c86

Browse files
uriyageranshidmadolson
authored
Offload TLS negotiation to I/O threads (#1338)
## TLS Negotiation Offloading to I/O Threads ### Overview This PR introduces the ability to offload TLS handshake negotiations to I/O threads, significantly improving performance under high TLS connection loads. ### Key Changes - Added infrastructure to offload TLS negotiations to I/O threads - Refactored SSL event handling to allow I/O threads modify conn flags. - Introduced new connection flag to identify client connections ### Performance Impact Testing with 650 clients with SET commands and 160 new TLS connections per second in the background: #### Throughput Impact of new TLS connections - **With Offloading**: Minimal impact (1050K → 990K ops/sec) - **Without Offloading**: Significant drop (1050K → 670K ops/sec) #### New Connection Rate - **With Offloading**: - 1,757 conn/sec - **Without Offloading**: - 477 conn/sec ### Implementation Details 1. **Main Thread**: - Initiates negotiation-offload jobs to I/O threads - Adds connections to pending-read clients list (using existing read offload mechanism) - Post-negotiation handling: - Creates read/write events if needed for incomplete negotiations - Calls accept handler for completed negotiations 2. **I/O Thread**: - Performs TLS negotiation - Updates connection flags based on negotiation result Related issue:#761 --------- Signed-off-by: Uri Yagelnik <[email protected]> Signed-off-by: ranshid <[email protected]> Co-authored-by: ranshid <[email protected]> Co-authored-by: Madelyn Olson <[email protected]>
1 parent e203ca3 commit 8060c86

File tree

8 files changed

+174
-70
lines changed

8 files changed

+174
-70
lines changed

.github/workflows/daily.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,44 @@ jobs:
375375
if: true && !contains(github.event.inputs.skiptests, 'cluster')
376376
run: ./runtest-cluster --io-threads ${{github.event.inputs.cluster_test_args}}
377377

378+
test-ubuntu-tls-io-threads:
379+
runs-on: ubuntu-latest
380+
if: |
381+
(github.event_name == 'workflow_dispatch' ||
382+
(github.event_name == 'schedule' && github.repository == 'valkey-io/valkey') ||
383+
(github.event_name == 'pull_request' && (contains(github.event.pull_request.labels.*.name, 'run-extra-tests') || github.event.pull_request.base.ref != 'unstable'))) &&
384+
!contains(github.event.inputs.skipjobs, 'tls') && !contains(github.event.inputs.skipjobs, 'iothreads')
385+
timeout-minutes: 14400
386+
steps:
387+
- name: prep
388+
if: github.event_name == 'workflow_dispatch'
389+
run: |
390+
echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV
391+
echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV
392+
echo "skipjobs: ${{github.event.inputs.skipjobs}}"
393+
echo "skiptests: ${{github.event.inputs.skiptests}}"
394+
echo "test_args: ${{github.event.inputs.test_args}}"
395+
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
396+
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
397+
with:
398+
repository: ${{ env.GITHUB_REPOSITORY }}
399+
ref: ${{ env.GITHUB_HEAD_REF }}
400+
- name: make
401+
run: |
402+
make BUILD_TLS=yes SERVER_CFLAGS='-Werror'
403+
- name: testprep
404+
run: |
405+
sudo apt-get install tcl8.6 tclx tcl-tls
406+
./utils/gen-test-certs.sh
407+
- name: test
408+
if: true && !contains(github.event.inputs.skiptests, 'valkey')
409+
run: |
410+
./runtest --io-threads --tls --accurate --verbose --tags network --dump-logs ${{github.event.inputs.test_args}}
411+
- name: cluster tests
412+
if: true && !contains(github.event.inputs.skiptests, 'cluster')
413+
run: |
414+
./runtest-cluster --io-threads --tls ${{github.event.inputs.cluster_test_args}}
415+
378416
test-ubuntu-reclaim-cache:
379417
runs-on: ubuntu-latest
380418
if: |

src/connection.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ typedef enum {
5454
CONN_STATE_ERROR
5555
} ConnectionState;
5656

57-
#define CONN_FLAG_CLOSE_SCHEDULED (1 << 0) /* Closed scheduled by a handler */
58-
#define CONN_FLAG_WRITE_BARRIER (1 << 1) /* Write barrier requested */
57+
#define CONN_FLAG_CLOSE_SCHEDULED (1 << 0) /* Closed scheduled by a handler */
58+
#define CONN_FLAG_WRITE_BARRIER (1 << 1) /* Write barrier requested */
59+
#define CONN_FLAG_ALLOW_ACCEPT_OFFLOAD (1 << 2) /* Connection accept can be offloaded to IO threads. */
5960

6061
#define CONN_TYPE_SOCKET "tcp"
6162
#define CONN_TYPE_UNIX "unix"

src/io_threads.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,3 +561,55 @@ void trySendPollJobToIOThreads(void) {
561561
aeSetPollProtect(server.el, 1);
562562
IOJobQueue_push(jq, IOThreadPoll, server.el);
563563
}
564+
565+
static void ioThreadAccept(void *data) {
566+
client *c = (client *)data;
567+
connAccept(c->conn, NULL);
568+
c->io_read_state = CLIENT_COMPLETED_IO;
569+
}
570+
571+
/*
572+
* Attempts to offload an Accept operation (currently used for TLS accept) for a client
573+
* connection to I/O threads.
574+
*
575+
* Returns:
576+
* C_OK - If the accept operation was successfully queued for processing
577+
* C_ERR - If the connection is not eligible for offloading
578+
*
579+
* Parameters:
580+
* conn - The connection object to perform the accept operation on
581+
*/
582+
int trySendAcceptToIOThreads(connection *conn) {
583+
if (server.io_threads_num <= 1) {
584+
return C_ERR;
585+
}
586+
587+
if (!(conn->flags & CONN_FLAG_ALLOW_ACCEPT_OFFLOAD)) {
588+
return C_ERR;
589+
}
590+
591+
client *c = connGetPrivateData(conn);
592+
if (c->io_read_state != CLIENT_IDLE) {
593+
return C_OK;
594+
}
595+
596+
if (server.active_io_threads_num <= 1) {
597+
return C_ERR;
598+
}
599+
600+
size_t thread_id = (c->id % (server.active_io_threads_num - 1)) + 1;
601+
IOJobQueue *job_queue = &io_jobs[thread_id];
602+
603+
if (IOJobQueue_isFull(job_queue)) {
604+
return C_ERR;
605+
}
606+
607+
c->io_read_state = CLIENT_PENDING_IO;
608+
c->flag.pending_read = 1;
609+
listLinkNodeTail(server.clients_pending_io_read, &c->pending_read_list_node);
610+
connSetPostponeUpdateState(c->conn, 1);
611+
server.stat_io_accept_offloaded++;
612+
IOJobQueue_push(job_queue, ioThreadAccept, c);
613+
614+
return C_OK;
615+
}

src/io_threads.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ int tryOffloadFreeArgvToIOThreads(client *c, int argc, robj **argv);
1313
void adjustIOThreadsByEventLoad(int numevents, int increase_only);
1414
void drainIOThreadsQueue(void);
1515
void trySendPollJobToIOThreads(void);
16+
int trySendAcceptToIOThreads(connection *conn);
1617

1718
#endif /* IO_THREADS_H */

src/networking.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ client *createClient(connection *conn) {
134134
if (server.tcpkeepalive) connKeepAlive(conn, server.tcpkeepalive);
135135
connSetReadHandler(conn, readQueryFromClient);
136136
connSetPrivateData(conn, c);
137+
conn->flags |= CONN_FLAG_ALLOW_ACCEPT_OFFLOAD;
137138
}
138139
c->buf = zmalloc_usable(PROTO_REPLY_CHUNK_BYTES, &c->buf_usable_size);
139140
selectDb(c, 0);
@@ -4805,9 +4806,14 @@ int processIOThreadsReadDone(void) {
48054806
processed++;
48064807
server.stat_io_reads_processed++;
48074808

4809+
/* Save the current conn state, as connUpdateState may modify it */
4810+
int in_accept_state = (connGetState(c->conn) == CONN_STATE_ACCEPTING);
48084811
connSetPostponeUpdateState(c->conn, 0);
48094812
connUpdateState(c->conn);
48104813

4814+
/* In accept state, no client's data was read - stop here. */
4815+
if (in_accept_state) continue;
4816+
48114817
/* On read error - stop here. */
48124818
if (handleReadResult(c) == C_ERR) {
48134819
continue;

src/server.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2645,6 +2645,7 @@ void resetServerStats(void) {
26452645
server.stat_total_reads_processed = 0;
26462646
server.stat_io_writes_processed = 0;
26472647
server.stat_io_freed_objects = 0;
2648+
server.stat_io_accept_offloaded = 0;
26482649
server.stat_poll_processed_by_io_threads = 0;
26492650
server.stat_total_writes_processed = 0;
26502651
server.stat_client_qbuf_limit_disconnections = 0;
@@ -5915,6 +5916,7 @@ sds genValkeyInfoString(dict *section_dict, int all_sections, int everything) {
59155916
"io_threaded_reads_processed:%lld\r\n", server.stat_io_reads_processed,
59165917
"io_threaded_writes_processed:%lld\r\n", server.stat_io_writes_processed,
59175918
"io_threaded_freed_objects:%lld\r\n", server.stat_io_freed_objects,
5919+
"io_threaded_accept_processed:%lld\r\n", server.stat_io_accept_offloaded,
59185920
"io_threaded_poll_processed:%lld\r\n", server.stat_poll_processed_by_io_threads,
59195921
"io_threaded_total_prefetch_batches:%lld\r\n", server.stat_total_prefetch_batches,
59205922
"io_threaded_total_prefetch_entries:%lld\r\n", server.stat_total_prefetch_entries,

src/server.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,7 @@ struct valkeyServer {
18691869
long long stat_io_reads_processed; /* Number of read events processed by IO threads */
18701870
long long stat_io_writes_processed; /* Number of write events processed by IO threads */
18711871
long long stat_io_freed_objects; /* Number of objects freed by IO threads */
1872+
long long stat_io_accept_offloaded; /* Number of offloaded accepts */
18721873
long long stat_poll_processed_by_io_threads; /* Total number of poll jobs processed by IO */
18731874
long long stat_total_reads_processed; /* Total number of read events processed */
18741875
long long stat_total_writes_processed; /* Total number of write events processed */

0 commit comments

Comments
 (0)