mirror of
https://github.com/curl/curl.git
synced 2026-08-24 23:13:38 +03:00
multi: handle errors returned from socket/timer callbacks
The callbacks were partially documented to support this. Now the behavior is documented and returning error from either of these callbacks will effectively kill all currently ongoing transfers. Added test 530 to verify Reported-by: Marcelo Juchem Fixes #8083 Closes #8089
This commit is contained in:
parent
b3f9c1aa09
commit
2b3dd01b77
14 changed files with 574 additions and 67 deletions
|
|
@ -75,7 +75,7 @@ test490 test491 test492 test493 test494 \
|
|||
test500 test501 test502 test503 test504 test505 test506 test507 test508 \
|
||||
test509 test510 test511 test512 test513 test514 test515 test516 test517 \
|
||||
test518 test519 test520 test521 test522 test523 test524 test525 test526 \
|
||||
test527 test528 test529 test531 test532 test533 test534 test535 \
|
||||
test527 test528 test529 test530 test531 test532 test533 test534 test535 \
|
||||
test537 test538 test539 test540 test541 test542 test543 test544 \
|
||||
test545 test546 test547 test548 test549 test550 test551 test552 test553 \
|
||||
test554 test555 test556 test557 test558 test559 test560 test561 test562 \
|
||||
|
|
|
|||
|
|
@ -144,7 +144,8 @@ m7: The easy handle is already added to a multi handle
|
|||
m8: API function called from within callback
|
||||
m9: Wakeup is unavailable or failed
|
||||
m10: A libcurl function was given a bad argument
|
||||
m11: Unknown error
|
||||
m11: Operation was aborted by an application callback
|
||||
m12: Unknown error
|
||||
s0: No error
|
||||
s1: Unknown share option
|
||||
s2: Share currently in use
|
||||
|
|
|
|||
49
tests/data/test530
Normal file
49
tests/data/test530
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
multi
|
||||
HTTP
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
HTTP/1.1 200 OK
|
||||
Date: Tue, 09 Nov 2010 14:49:00 GMT
|
||||
Server: test-server/fake
|
||||
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
|
||||
ETag: "21025-dc7-39462498"
|
||||
Accept-Ranges: bytes
|
||||
Content-Length: 6007
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
Funny-head: yesyes
|
||||
|
||||
-foo-
|
||||
%repeat[1000 x foobar]%
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<tool>
|
||||
lib%TESTNUMBER
|
||||
</tool>
|
||||
<name>
|
||||
multi_socket interface transfer with callbacks returning error
|
||||
</name>
|
||||
<command>
|
||||
http://%HOSTIP:%HTTPPORT/file%TESTNUMBER
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol>
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
|
|
@ -39,7 +39,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
|
|||
chkdecimalpoint libprereq \
|
||||
lib500 lib501 lib502 lib503 lib504 lib505 lib506 lib507 lib508 lib509 \
|
||||
lib510 lib511 lib512 lib513 lib514 lib515 lib516 lib517 lib518 lib519 \
|
||||
lib520 lib521 lib523 lib524 lib525 lib526 lib527 lib529 lib532 \
|
||||
lib520 lib521 lib523 lib524 lib525 lib526 lib527 lib529 lib530 lib532 \
|
||||
lib533 lib537 lib539 lib540 lib541 lib542 lib543 lib544 lib545 \
|
||||
lib547 lib548 lib549 lib552 lib553 lib554 lib555 lib556 lib557 lib558 \
|
||||
lib559 lib560 lib562 lib564 lib565 lib566 lib567 lib568 lib569 lib570 \
|
||||
|
|
@ -179,6 +179,10 @@ lib529_SOURCES = lib525.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) $(MULTIBYTE)
|
|||
lib529_LDADD = $(TESTUTIL_LIBS)
|
||||
lib529_CPPFLAGS = $(AM_CPPFLAGS) -DLIB529
|
||||
|
||||
lib530_SOURCES = lib530.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
|
||||
lib530_LDADD = $(TESTUTIL_LIBS)
|
||||
lib530_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
|
||||
lib532_SOURCES = lib526.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
|
||||
lib532_LDADD = $(TESTUTIL_LIBS)
|
||||
lib532_CPPFLAGS = $(AM_CPPFLAGS) -DLIB532
|
||||
|
|
|
|||
371
tests/libtest/lib530.c
Normal file
371
tests/libtest/lib530.c
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* Project ___| | | | _ \| |
|
||||
* / __| | | | |_) | |
|
||||
* | (__| |_| | _ <| |___
|
||||
* \___|\___/|_| \_\_____|
|
||||
*
|
||||
* Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution. The terms
|
||||
* are also available at https://curl.se/docs/copyright.html.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of the Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the COPYING file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
/*
|
||||
* The purpose of this test is to make sure that if CURLMOPT_SOCKETFUNCTION or
|
||||
* CURLMOPT_TIMERFUNCTION returns error, the associated transfer should be
|
||||
* aborted correctly.
|
||||
*/
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "testutil.h"
|
||||
#include "warnless.h"
|
||||
#include "memdebug.h"
|
||||
|
||||
#define TEST_HANG_TIMEOUT 60 * 1000
|
||||
|
||||
struct Sockets
|
||||
{
|
||||
curl_socket_t *sockets;
|
||||
int count; /* number of sockets actually stored in array */
|
||||
int max_count; /* max number of sockets that fit in allocated array */
|
||||
};
|
||||
|
||||
struct ReadWriteSockets
|
||||
{
|
||||
struct Sockets read, write;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a file descriptor from a sockets array.
|
||||
*/
|
||||
static void removeFd(struct Sockets *sockets, curl_socket_t fd, int mention)
|
||||
{
|
||||
int i;
|
||||
|
||||
if(mention)
|
||||
fprintf(stderr, "Remove socket fd %d\n", (int) fd);
|
||||
|
||||
for(i = 0; i < sockets->count; ++i) {
|
||||
if(sockets->sockets[i] == fd) {
|
||||
if(i < sockets->count - 1)
|
||||
memmove(&sockets->sockets[i], &sockets->sockets[i + 1],
|
||||
sizeof(curl_socket_t) * (sockets->count - (i + 1)));
|
||||
--sockets->count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file descriptor to a sockets array.
|
||||
*/
|
||||
static void addFd(struct Sockets *sockets, curl_socket_t fd, const char *what)
|
||||
{
|
||||
/**
|
||||
* To ensure we only have each file descriptor once, we remove it then add
|
||||
* it again.
|
||||
*/
|
||||
fprintf(stderr, "Add socket fd %d for %s\n", (int) fd, what);
|
||||
removeFd(sockets, fd, 0);
|
||||
/*
|
||||
* Allocate array storage when required.
|
||||
*/
|
||||
if(!sockets->sockets) {
|
||||
sockets->sockets = malloc(sizeof(curl_socket_t) * 20U);
|
||||
if(!sockets->sockets)
|
||||
return;
|
||||
sockets->max_count = 20;
|
||||
}
|
||||
else if(sockets->count + 1 > sockets->max_count) {
|
||||
curl_socket_t *oldptr = sockets->sockets;
|
||||
sockets->sockets = realloc(oldptr, sizeof(curl_socket_t) *
|
||||
(sockets->max_count + 20));
|
||||
if(!sockets->sockets) {
|
||||
/* cleanup in test_cleanup */
|
||||
sockets->sockets = oldptr;
|
||||
return;
|
||||
}
|
||||
sockets->max_count += 20;
|
||||
}
|
||||
/*
|
||||
* Add file descriptor to array.
|
||||
*/
|
||||
sockets->sockets[sockets->count] = fd;
|
||||
++sockets->count;
|
||||
}
|
||||
|
||||
static int max_socket_calls;
|
||||
static int socket_calls = 0;
|
||||
|
||||
/**
|
||||
* Callback invoked by curl to poll reading / writing of a socket.
|
||||
*/
|
||||
static int curlSocketCallback(CURL *easy, curl_socket_t s, int action,
|
||||
void *userp, void *socketp)
|
||||
{
|
||||
struct ReadWriteSockets *sockets = userp;
|
||||
|
||||
(void)easy; /* unused */
|
||||
(void)socketp; /* unused */
|
||||
|
||||
fprintf(stderr, "CURLMOPT_SOCKETFUNCTION called: %u\n", socket_calls++);
|
||||
if(socket_calls == max_socket_calls) {
|
||||
fprintf(stderr, "curlSocketCallback returns error\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(action == CURL_POLL_IN || action == CURL_POLL_INOUT)
|
||||
addFd(&sockets->read, s, "read");
|
||||
|
||||
if(action == CURL_POLL_OUT || action == CURL_POLL_INOUT)
|
||||
addFd(&sockets->write, s, "write");
|
||||
|
||||
if(action == CURL_POLL_REMOVE) {
|
||||
removeFd(&sockets->read, s, 1);
|
||||
removeFd(&sockets->write, s, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max_timer_calls;
|
||||
static int timer_calls = 0;
|
||||
|
||||
/**
|
||||
* Callback invoked by curl to set a timeout.
|
||||
*/
|
||||
static int curlTimerCallback(CURLM *multi, long timeout_ms, void *userp)
|
||||
{
|
||||
struct timeval *timeout = userp;
|
||||
|
||||
(void)multi; /* unused */
|
||||
fprintf(stderr, "CURLMOPT_TIMERFUNCTION called: %u\n", timer_calls++);
|
||||
if(timer_calls == max_timer_calls) {
|
||||
fprintf(stderr, "curlTimerCallback returns error\n");
|
||||
return -1;
|
||||
}
|
||||
if(timeout_ms != -1) {
|
||||
*timeout = tutil_tvnow();
|
||||
timeout->tv_usec += timeout_ms * 1000;
|
||||
}
|
||||
else {
|
||||
timeout->tv_sec = -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for curl completion.
|
||||
*/
|
||||
static int checkForCompletion(CURLM *curl, int *success)
|
||||
{
|
||||
int numMessages;
|
||||
CURLMsg *message;
|
||||
int result = 0;
|
||||
*success = 0;
|
||||
while((message = curl_multi_info_read(curl, &numMessages)) != NULL) {
|
||||
if(message->msg == CURLMSG_DONE) {
|
||||
result = 1;
|
||||
if(message->data.result == CURLE_OK)
|
||||
*success = 1;
|
||||
else
|
||||
*success = 0;
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "Got an unexpected message from curl: %i\n",
|
||||
(int)message->msg);
|
||||
result = 1;
|
||||
*success = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getMicroSecondTimeout(struct timeval *timeout)
|
||||
{
|
||||
struct timeval now;
|
||||
ssize_t result;
|
||||
now = tutil_tvnow();
|
||||
result = (ssize_t)((timeout->tv_sec - now.tv_sec) * 1000000 +
|
||||
timeout->tv_usec - now.tv_usec);
|
||||
if(result < 0)
|
||||
result = 0;
|
||||
|
||||
return curlx_sztosi(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a fd_set with all of the sockets in use.
|
||||
*/
|
||||
static void updateFdSet(struct Sockets *sockets, fd_set* fdset,
|
||||
curl_socket_t *maxFd)
|
||||
{
|
||||
int i;
|
||||
for(i = 0; i < sockets->count; ++i) {
|
||||
FD_SET(sockets->sockets[i], fdset);
|
||||
if(*maxFd < sockets->sockets[i] + 1) {
|
||||
*maxFd = sockets->sockets[i] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void notifyCurl(CURLM *curl, curl_socket_t s, int evBitmask,
|
||||
const char *info)
|
||||
{
|
||||
int numhandles = 0;
|
||||
CURLMcode result = curl_multi_socket_action(curl, s, evBitmask, &numhandles);
|
||||
if(result != CURLM_OK) {
|
||||
fprintf(stderr, "Curl error on %s: %i (%s)\n",
|
||||
info, result, curl_multi_strerror(result));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke curl when a file descriptor is set.
|
||||
*/
|
||||
static void checkFdSet(CURLM *curl, struct Sockets *sockets, fd_set *fdset,
|
||||
int evBitmask, const char *name)
|
||||
{
|
||||
int i;
|
||||
for(i = 0; i < sockets->count; ++i) {
|
||||
if(FD_ISSET(sockets->sockets[i], fdset)) {
|
||||
notifyCurl(curl, sockets->sockets[i], evBitmask, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int testone(char *URL, int timercb, int socketcb)
|
||||
{
|
||||
int res = 0;
|
||||
CURL *curl = NULL; CURLM *m = NULL;
|
||||
struct ReadWriteSockets sockets = {{NULL, 0, 0}, {NULL, 0, 0}};
|
||||
struct timeval timeout = {-1, 0};
|
||||
int success = 0;
|
||||
|
||||
/* set the limits */
|
||||
max_timer_calls = timercb;
|
||||
max_socket_calls = socketcb;
|
||||
timer_calls = 0; /* reset the globals */
|
||||
socket_calls = 0;
|
||||
|
||||
fprintf(stderr, "start test: %d %d\n", timercb, socketcb);
|
||||
start_test_timing();
|
||||
|
||||
res_global_init(CURL_GLOBAL_ALL);
|
||||
if(res)
|
||||
return res;
|
||||
|
||||
easy_init(curl);
|
||||
|
||||
/* specify target */
|
||||
easy_setopt(curl, CURLOPT_URL, URL);
|
||||
|
||||
/* go verbose */
|
||||
easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
|
||||
multi_init(m);
|
||||
|
||||
multi_setopt(m, CURLMOPT_SOCKETFUNCTION, curlSocketCallback);
|
||||
multi_setopt(m, CURLMOPT_SOCKETDATA, &sockets);
|
||||
|
||||
multi_setopt(m, CURLMOPT_TIMERFUNCTION, curlTimerCallback);
|
||||
multi_setopt(m, CURLMOPT_TIMERDATA, &timeout);
|
||||
|
||||
multi_add_handle(m, curl);
|
||||
|
||||
notifyCurl(m, CURL_SOCKET_TIMEOUT, 0, "timeout");
|
||||
|
||||
while(!checkForCompletion(m, &success)) {
|
||||
fd_set readSet, writeSet;
|
||||
curl_socket_t maxFd = 0;
|
||||
struct timeval tv = {10, 0};
|
||||
|
||||
FD_ZERO(&readSet);
|
||||
FD_ZERO(&writeSet);
|
||||
updateFdSet(&sockets.read, &readSet, &maxFd);
|
||||
updateFdSet(&sockets.write, &writeSet, &maxFd);
|
||||
|
||||
if(timeout.tv_sec != -1) {
|
||||
int usTimeout = getMicroSecondTimeout(&timeout);
|
||||
tv.tv_sec = usTimeout / 1000000;
|
||||
tv.tv_usec = usTimeout % 1000000;
|
||||
}
|
||||
else if(maxFd <= 0) {
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 100000;
|
||||
}
|
||||
|
||||
select_test((int)maxFd, &readSet, &writeSet, NULL, &tv);
|
||||
|
||||
/* Check the sockets for reading / writing */
|
||||
checkFdSet(m, &sockets.read, &readSet, CURL_CSELECT_IN, "read");
|
||||
checkFdSet(m, &sockets.write, &writeSet, CURL_CSELECT_OUT, "write");
|
||||
|
||||
if(timeout.tv_sec != -1 && getMicroSecondTimeout(&timeout) == 0) {
|
||||
/* Curl's timer has elapsed. */
|
||||
notifyCurl(m, CURL_SOCKET_TIMEOUT, 0, "timeout");
|
||||
}
|
||||
|
||||
abort_on_test_timeout();
|
||||
}
|
||||
|
||||
if(!success) {
|
||||
fprintf(stderr, "Error getting file.\n");
|
||||
res = TEST_ERR_MAJOR_BAD;
|
||||
}
|
||||
|
||||
test_cleanup:
|
||||
|
||||
/* proper cleanup sequence */
|
||||
fprintf(stderr, "cleanup: %d %d\n", timercb, socketcb);
|
||||
curl_multi_remove_handle(m, curl);
|
||||
curl_easy_cleanup(curl);
|
||||
curl_multi_cleanup(m);
|
||||
curl_global_cleanup();
|
||||
|
||||
/* free local memory */
|
||||
free(sockets.read.sockets);
|
||||
free(sockets.write.sockets);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int test(char *URL)
|
||||
{
|
||||
int rc;
|
||||
/* rerun the same transfer multiple times and make it fail in different
|
||||
callback calls */
|
||||
rc = testone(URL, 0, 0);
|
||||
if(rc)
|
||||
fprintf(stderr, "test 0/0 failed: %d\n", rc);
|
||||
|
||||
rc = testone(URL, 1, 0);
|
||||
if(!rc)
|
||||
fprintf(stderr, "test 1/0 failed: %d\n", rc);
|
||||
|
||||
rc = testone(URL, 2, 0);
|
||||
if(!rc)
|
||||
fprintf(stderr, "test 2/0 failed: %d\n", rc);
|
||||
|
||||
rc = testone(URL, 0, 1);
|
||||
if(!rc)
|
||||
fprintf(stderr, "test 0/1 failed: %d\n", rc);
|
||||
|
||||
rc = testone(URL, 0, 2);
|
||||
if(!rc)
|
||||
fprintf(stderr, "test 0/2 failed: %d\n", rc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue