Ticket #1079: web.patch

File web.patch, 11.5 KB (added by alexat, 10 years ago)
  • libtransmission/web.c

     
    5959****
    6060***/
    6161
     62enum {
     63    DID_CONNECT = 1,
     64    DID_TIMEOUT = 2,
     65    TASK_PAUSABLE = 4,
     66    TASK_PAUSED = 8
     67};
     68
    6269struct tr_web_task
    6370{
    6471    long code;
    6572    long timeout_secs;
    66     bool did_connect;
    67     bool did_timeout;
     73    char state;
    6874    struct evbuffer * response;
    6975    struct evbuffer * freebuf;
    7076    char * url;
     
    9298****
    9399***/
    94100
     101enum web_command_type
     102{
     103    ADD_WEB_TASK,
     104    REMOVE_WEB_TASK,
     105    PAUSE_WEB_TASK,
     106    UNPAUSE_WEB_TASK,
     107    WEB_WAKEUP
     108};
     109
     110struct web_command
     111{
     112    enum web_command_type type;
     113    struct tr_web_task * task;
     114};
     115
    95116struct tr_web
    96117{
    97118    bool curl_verbose;
    98119    int close_mode;
    99     struct tr_web_task * tasks;
    100     tr_lock * taskLock;
    101120    char * cookie_filename;
     121    int command_pipe;
     122    tr_thread * thread;
    102123};
    103124
    104125/***
     
    110131{
    111132    const size_t byteCount = size * nmemb;
    112133    struct tr_web_task * task = vtask;
     134    task->state |= TASK_PAUSABLE;
    113135    evbuffer_add( task->response, ptr, byteCount );
    114136    dbgmsg( "wrote %zu bytes to task %p's buffer", byteCount, task );
    115137    return byteCount;
     
    161183    task->timeout_secs = getTimeoutFromURL( task );
    162184
    163185    curl_easy_setopt( e, CURLOPT_AUTOREFERER, 1L );
     186    curl_easy_setopt( e, CURLOPT_BUFFERSIZE, 1024L );
    164187    curl_easy_setopt( e, CURLOPT_COOKIEFILE, web->cookie_filename );
    165188    curl_easy_setopt( e, CURLOPT_ENCODING, "gzip;q=1.0, deflate, identity" );
    166189    curl_easy_setopt( e, CURLOPT_FOLLOWLOCATION, 1L );
     
    194217    return e;
    195218}
    196219
     220static inline void
     221sendToWebThread( struct tr_web * web,
     222                 enum web_command_type type,
     223                 struct tr_web_task * task )
     224{
     225    struct web_command cmd = { type, task };
     226    tr_pipewrite( web->command_pipe, &cmd, sizeof( cmd ));
     227}
     228
    197229/***
    198230****
    199231***/
     
    206238
    207239    if( task->done_func != NULL )
    208240        task->done_func( task->session,
    209                          task->did_connect,
    210                          task->did_timeout,
     241                         task->state & DID_CONNECT,
     242                         task->state & DID_TIMEOUT,
    211243                         task->code,
    212244                         evbuffer_pullup( task->response, -1 ),
    213245                         evbuffer_get_length( task->response ),
    214246                         task->done_func_user_data );
    215247
    216     task_free( task );
     248    /* unpause commands could still be pending, so clean this task up
     249    in the web thread to avoid access violations */
     250    sendToWebThread( task->session->web, REMOVE_WEB_TASK, task );
    217251}
    218252
    219253/****
     
    256290        task->done_func_user_data = done_func_user_data;
    257291        task->response = buffer ? buffer : evbuffer_new( );
    258292        task->freebuf = buffer ? NULL : task->response;
     293        sendToWebThread( web, ADD_WEB_TASK, task );
    259294
    260         tr_lockLock( web->taskLock );
    261         task->next = web->tasks;
    262         web->tasks = task;
    263         tr_lockUnlock( web->taskLock );
    264295        return task;
    265296    }
    266297    return NULL;
     
    303334tr_webThreadFunc( void * vsession )
    304335{
    305336    CURLM * multi;
    306     struct tr_web * web;
    307337    int taskCount = 0;
    308     struct tr_web_task * task;
    309338    tr_session * session = vsession;
     339    struct tr_web * web = session->web;
     340    int pipe_fds[2];
    310341
    311342    /* try to enable ssl for https support; but if that fails,
    312343     * try a plain vanilla init */
    313344    if( curl_global_init( CURL_GLOBAL_SSL ) )
    314345        curl_global_init( 0 );
    315346
    316     web = tr_new0( struct tr_web, 1 );
    317     web->close_mode = ~0;
    318     web->taskLock = tr_lockNew( );
    319     web->tasks = NULL;
    320     web->curl_verbose = getenv( "TR_CURL_VERBOSE" ) != NULL;
    321     web->cookie_filename = tr_buildPath( session->configDir, "cookies.txt", NULL );
    322 
    323347    multi = curl_multi_init( );
    324     session->web = web;
    325348
     349    tr_pipe( pipe_fds );
     350    tr_set_nonblocking( pipe_fds[0] );
     351    tr_set_nonblocking( pipe_fds[1] );
     352    web->command_pipe = pipe_fds[1];
     353
    326354    for( ;; )
    327355    {
     356        struct web_command cmd;
    328357        long msec;
    329358        int unused;
    330359        CURLMsg * msg;
    331360        CURLMcode mcode;
    332361
    333         if( web->close_mode == TR_WEB_CLOSE_NOW )
    334             break;
    335         if( ( web->close_mode == TR_WEB_CLOSE_WHEN_IDLE ) && ( web->tasks == NULL ) )
    336             break;
    337 
    338         /* add tasks from the queue */
    339         tr_lockLock( web->taskLock );
    340         while( web->tasks != NULL )
    341         {
    342             /* pop the task */
    343             task = web->tasks;
    344             web->tasks = task->next;
    345             task->next = NULL;
    346 
    347             dbgmsg( "adding task to curl: [%s]", task->url );
    348             curl_multi_add_handle( multi, createEasy( session, web, task ));
    349             /*fprintf( stderr, "adding a task.. taskCount is now %d\n", taskCount );*/
    350             ++taskCount;
    351         }
    352         tr_lockUnlock( web->taskLock );
    353 
    354362        /* maybe wait a little while before calling curl_multi_perform() */
    355363        msec = 0;
    356364        curl_multi_timeout( multi, &msec );
    357365        if( msec < 0 )
    358366            msec = THREADFUNC_MAX_SLEEP_MSEC;
    359367        if( session->isClosed )
    360             msec = 100; /* on shutdown, call perform() more frequently */
     368            msec = MIN( msec, 100 ); /* on shutdown, call perform() more frequently */
    361369        if( msec > 0 )
    362370        {
    363371            int usec;
     
    371379            FD_ZERO( &c_fd_set );
    372380            curl_multi_fdset( multi, &r_fd_set, &w_fd_set, &c_fd_set, &max_fd );
    373381
     382            FD_SET( pipe_fds[0], &r_fd_set );
     383            max_fd = MAX( max_fd, pipe_fds[0] );
     384
    374385            if( msec > THREADFUNC_MAX_SLEEP_MSEC )
    375386                msec = THREADFUNC_MAX_SLEEP_MSEC;
    376387
     
    380391            tr_select( max_fd+1, &r_fd_set, &w_fd_set, &c_fd_set, &t );
    381392        }
    382393
     394        while( tr_piperead( pipe_fds[0], &cmd, sizeof( cmd )) == sizeof( cmd ))
     395        {
     396            switch( cmd.type )
     397            {
     398                case ADD_WEB_TASK:
     399                    if( web->close_mode == TR_WEB_CLOSE_NOW )
     400                    {
     401                        /* Discard any remaining tasks.
     402                        * This is rare, but can happen on shutdown with unresponsive trackers. */
     403                        dbgmsg( "Discarding task \"%s\"", cmd.task->url );
     404                        task_free( cmd.task );
     405                    }
     406                    else
     407                    {
     408                        dbgmsg( "adding task to curl: [%s]", cmd.task->url );
     409                        curl_multi_add_handle( multi, createEasy( session, web, cmd.task ));
     410                        /*fprintf( stderr, "adding a task.. taskCount is now %d\n", taskCount );*/
     411                        ++taskCount;
     412                    }
     413                    break;
     414                case REMOVE_WEB_TASK:
     415                    curl_easy_cleanup( cmd.task->curl_easy );
     416                    task_free( cmd.task );
     417                    --taskCount;
     418                    break;
     419                case PAUSE_WEB_TASK:
     420                case UNPAUSE_WEB_TASK:
     421                    if( cmd.task->curl_easy != NULL && web->close_mode != TR_WEB_CLOSE_NOW )
     422                        curl_easy_pause( cmd.task->curl_easy, cmd.type == PAUSE_WEB_TASK ?
     423                                         CURLPAUSE_ALL : CURLPAUSE_CONT );
     424                    break;
     425                case WEB_WAKEUP:
     426                    break;
     427            }
     428        }
     429
     430        if( web->close_mode == TR_WEB_CLOSE_NOW )
     431            break;
     432        if( ( web->close_mode == TR_WEB_CLOSE_WHEN_IDLE ) && ( taskCount == 0 ) )
     433            break;
     434
    383435        /* call curl_multi_perform() */
    384436        do {
    385437            mcode = curl_multi_perform( multi, &unused );
     
    398450                curl_easy_getinfo( e, CURLINFO_RESPONSE_CODE, &task->code );
    399451                curl_easy_getinfo( e, CURLINFO_REQUEST_SIZE, &req_bytes_sent );
    400452                curl_easy_getinfo( e, CURLINFO_TOTAL_TIME, &total_time );
    401                 task->did_connect = task->code>0 || req_bytes_sent>0;
    402                 task->did_timeout = !task->code && ( total_time >= task->timeout_secs );
     453                if( task->code>0 || req_bytes_sent>0 )
     454                    task->state |= DID_CONNECT;
     455                if( !task->code && ( total_time >= task->timeout_secs ) )
     456                    task->state |= DID_TIMEOUT;
    403457                curl_multi_remove_handle( multi, e );
    404                 curl_easy_cleanup( e );
    405458/*fprintf( stderr, "removing a completed task.. taskCount is now %d (response code: %d, response len: %d)\n", taskCount, (int)task->code, (int)evbuffer_get_length(task->response) );*/
    406459                tr_runInEventThread( task->session, task_finish_func, task );
    407                 --taskCount;
    408460            }
    409461        }
    410462    }
    411463
    412     /* Discard any remaining tasks.
    413      * This is rare, but can happen on shutdown with unresponsive trackers. */
    414     while( web->tasks != NULL ) {
    415         task = web->tasks;
    416         web->tasks = task->next;
    417         dbgmsg( "Discarding task \"%s\"", task->url );
    418         task_free( task );
    419     }
    420 
    421464    /* cleanup */
     465    close( pipe_fds[0] );
     466    close( pipe_fds[1] );
    422467    curl_multi_cleanup( multi );
    423     tr_lockFree( web->taskLock );
    424468    tr_free( web->cookie_filename );
    425469    tr_free( web );
    426470    session->web = NULL;
     
    429473void
    430474tr_webInit( tr_session * session )
    431475{
    432     tr_threadNew( tr_webThreadFunc, session );
     476    struct tr_web * web = tr_new0( struct tr_web, 1 );
     477    session->web = web;
     478    web->close_mode = ~0;
     479    web->curl_verbose = getenv( "TR_CURL_VERBOSE" ) != NULL;
     480    web->cookie_filename = tr_buildPath( session->configDir, "cookies.txt", NULL );
     481    web->thread = tr_threadNew( tr_webThreadFunc, session );
    433482}
    434483
    435484void
     
    440489        session->web->close_mode = close_mode;
    441490
    442491        if( close_mode == TR_WEB_CLOSE_NOW )
     492        {
     493            sendToWebThread( session->web, WEB_WAKEUP, NULL );
    443494            while( session->web != NULL )
    444495                tr_wait_msec( 100 );
     496        }
    445497    }
    446498}
    447499
     
    451503    curl_easy_getinfo( task->curl_easy, (CURLINFO) info, dst );
    452504}
    453505
     506void
     507tr_webPauseTask( struct tr_web_task * task,
     508                 bool paused )
     509{
     510    struct tr_web * web = task->session->web;
     511
     512    /* curl won't pause a handle before actually having received data - catch this */
     513    if( !( task->state & TASK_PAUSABLE ) || paused == ( task->state & TASK_PAUSED ) )
     514        return;
     515
     516    if( tr_amInThread( web->thread ) && task->curl_easy != NULL ) {
     517/*        fprintf (stderr, "%s curl easy\n", paused ? "Pausing" : "Unpausing"); */
     518        curl_easy_pause( task->curl_easy, paused ? CURLPAUSE_ALL : CURLPAUSE_CONT );
     519    }
     520    else {
     521/*        fprintf (stderr, "Sending curl easy %s command to web thread\n", paused ? "pause" : "unpause"); */
     522        sendToWebThread( web, paused ? PAUSE_WEB_TASK : UNPAUSE_WEB_TASK, task );
     523    }
     524
     525    if( paused )
     526        task->state |= TASK_PAUSED;
     527    else
     528        task->state &= ~TASK_PAUSED;
     529}
     530
    454531/*****
    455532******
    456533******
  • libtransmission/web.h

     
    7070
    7171void tr_webGetTaskInfo( struct tr_web_task * task, tr_web_task_info info, void * dst );
    7272
     73void tr_webPauseTask( struct tr_web_task * task, bool paused );
     74
    7375void tr_http_escape( struct evbuffer *out, const char *str, int len, bool escape_slashes );
    7476
    7577void tr_http_escape_sha1( char * out, const uint8_t * sha1_digest );