I did this
Debian discovered this while running the pycurl tests against libcurl 8.22.0-rc2.
The test opens a WebSocket transfer with curl_multi_* and a WRITEFUNCTION that, on receiving a specific frame, calls curl_ws_send() (to reply) and then deliberately returns a short write (fewer bytes than it was handed) to abort the transfer with CURLE_WRITE_ERROR.
Port of the pycurl test to C:
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
static int cb_calls = 0;
static int sent_calls = 0;
static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
{
CURL *easy = (CURL *)userdata;
size_t len = size * nmemb;
fprintf(stderr, "[client] write_cb invoked, len=%zu data=%.*s\n",
len, (int)len, ptr);
cb_calls++;
if (len == 2 && memcmp(ptr, "hi", 2) == 0) {
size_t sent = 0;
CURLcode res = curl_ws_send(easy, "ack", 3, &sent, 0, CURLWS_BINARY);
fprintf(stderr, "[client] ws_send returned res=%d sent=%zu (call #%d)\n",
res, sent, ++sent_calls);
/* Deliberately report a short write to abort the transfer. */
return 0;
}
return len;
}
int main(int argc, char **argv)
{
const char *url = argc > 1 ? argv[1] : "ws://127.0.0.1:9999/greet-and-echo-reply";
curl_global_trace("ws,write,all");
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL *easy = curl_easy_init();
CURLM *multi = curl_multi_init();
curl_easy_setopt(easy, CURLOPT_URL, url);
curl_easy_setopt(easy, CURLOPT_WRITEFUNCTION, write_cb);
curl_easy_setopt(easy, CURLOPT_WRITEDATA, easy);
curl_easy_setopt(easy, CURLOPT_VERBOSE, 1L);
curl_multi_add_handle(multi, easy);
int still_running = 1;
while (still_running) {
CURLMcode mc = curl_multi_perform(multi, &still_running);
if (mc != CURLM_OK) break;
if (still_running) {
curl_multi_wait(multi, NULL, 0, 100, NULL);
}
}
int msgq = 0;
CURLMsg *msg;
while ((msg = curl_multi_info_read(multi, &msgq))) {
if (msg->msg == CURLMSG_DONE) {
fprintf(stderr, "[client] transfer done, result=%d (%s)\n",
msg->data.result, curl_easy_strerror(msg->data.result));
}
}
fprintf(stderr, "\n=== SUMMARY: write_cb called %d times, ws_send called %d times ===\n",
cb_calls, sent_calls);
curl_multi_remove_handle(multi, easy);
curl_easy_cleanup(easy);
curl_multi_cleanup(multi);
curl_global_cleanup();
return 0;
}
With libcurl 8.22.0-rc2, the write_cb is invoked twice.
I expected the following
write_cb is only invoked once.
Bisection reveals the double callback started with b940cb1.
curl/libcurl version
curl 8.22.0-DEV (Linux) libcurl/8.22.0-DEV OpenSSL/3.5.7 zlib/1.3.1.zlib-ng brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 nghttp2/1.68.0
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt mqtts pop3 pop3s rtsp smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz PSL SSL threadsafe UnixSockets zstd
operating system
Linux 7.1.6-201.fc44.x86_64 #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux (Fedora)
gcc (GCC) 16.1.1 20260515 (Red Hat 16.1.1-2)
OpenSSL 3.5.7
I did this
Debian discovered this while running the pycurl tests against libcurl 8.22.0-rc2.
The test opens a WebSocket transfer with curl_multi_* and a WRITEFUNCTION that, on receiving a specific frame, calls curl_ws_send() (to reply) and then deliberately returns a short write (fewer bytes than it was handed) to abort the transfer with CURLE_WRITE_ERROR.
Port of the pycurl test to C:
With libcurl 8.22.0-rc2, the write_cb is invoked twice.
I expected the following
write_cb is only invoked once.
Bisection reveals the double callback started with b940cb1.
curl/libcurl version
curl 8.22.0-DEV (Linux) libcurl/8.22.0-DEV OpenSSL/3.5.7 zlib/1.3.1.zlib-ng brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 nghttp2/1.68.0
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt mqtts pop3 pop3s rtsp smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz PSL SSL threadsafe UnixSockets zstd
operating system
Linux 7.1.6-201.fc44.x86_64 #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux (Fedora)
gcc (GCC) 16.1.1 20260515 (Red Hat 16.1.1-2)
OpenSSL 3.5.7