background_worker.c 8.08 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*****************************************************************************
 * Copyright (C) 2017 VLC authors and VideoLAN
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

23
#include <assert.h>
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
#include <vlc_common.h>
#include <vlc_threads.h>
#include <vlc_arrays.h>

#include "libvlc.h"
#include "background_worker.h"

struct bg_queued_item {
    void* id; /**< id associated with entity */
    void* entity; /**< the entity to process */
    int timeout; /**< timeout duration in microseconds */
};

struct background_worker {
    void* owner;
    struct background_worker_config conf;

41
    vlc_mutex_t lock; /**< acquire to inspect members that follow */
42 43 44
    struct {
        bool probe_request; /**< true if a probe is requested */
        vlc_cond_t wait; /**< wait for update in terms of head */
45
        vlc_cond_t worker_wait; /**< wait for probe request or cancelation */
46 47 48 49 50 51
        mtime_t deadline; /**< deadline of the current task */
        void* id; /**< id of the current task */
        bool active; /**< true if there is an active thread */
    } head;

    struct {
52
        vlc_cond_t wait; /**< wait for update in terms of tail */
53 54 55 56 57 58 59 60 61 62 63 64 65
        vlc_array_t data; /**< queue of pending entities to process */
    } tail;
};

static void* Thread( void* data )
{
    struct background_worker* worker = data;

    for( ;; )
    {
        struct bg_queued_item* item = NULL;
        void* handle;

66
        vlc_mutex_lock( &worker->lock );
67
        for( ;; )
68 69 70 71 72 73 74 75 76
        {
            if( vlc_array_count( &worker->tail.data ) )
            {
                item = vlc_array_item_at_index( &worker->tail.data, 0 );
                handle = NULL;

                vlc_array_remove( &worker->tail.data, 0 );
            }

77 78
            if( worker->head.deadline == VLC_TS_0 && item == NULL )
                worker->head.active = false;
79
            worker->head.id = item ? item->id : NULL;
80
            vlc_cond_broadcast( &worker->head.wait );
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

            if( item )
            {
                if( item->timeout > 0 )
                    worker->head.deadline = mdate() + item->timeout * 1000;
                else
                    worker->head.deadline = INT64_MAX;
            }
            else if( worker->head.deadline != VLC_TS_0 )
            {
                /* Wait 1 seconds for new inputs before terminating */
                mtime_t deadline = mdate() + INT64_C(1000000);
                int ret = vlc_cond_timedwait( &worker->tail.wait,
                                              &worker->lock, deadline );
                if( ret != 0 )
                {
                    /* Timeout: if there is still no items, the thread will be
                     * terminated at next loop iteration (active = false). */
                    worker->head.deadline = VLC_TS_0;
                }
                continue;
            }
            break;
104 105
        }

106 107 108
        if( !worker->head.active )
        {
            vlc_mutex_unlock( &worker->lock );
109
            break;
110 111 112 113
        }
        vlc_mutex_unlock( &worker->lock );

        assert( item != NULL );
114 115 116 117 118 119 120 121 122 123

        if( worker->conf.pf_start( worker->owner, item->entity, &handle ) )
        {
            worker->conf.pf_release( item->entity );
            free( item );
            continue;
        }

        for( ;; )
        {
124
            vlc_mutex_lock( &worker->lock );
125 126 127 128

            bool const b_timeout = worker->head.deadline <= mdate();
            worker->head.probe_request = false;

129
            vlc_mutex_unlock( &worker->lock );
130 131 132 133 134 135 136 137 138 139

            if( b_timeout ||
                worker->conf.pf_probe( worker->owner, handle ) )
            {
                worker->conf.pf_stop( worker->owner, handle );
                worker->conf.pf_release( item->entity );
                free( item );
                break;
            }

140
            vlc_mutex_lock( &worker->lock );
141 142 143
            if( worker->head.probe_request == false &&
                worker->head.deadline > mdate() )
            {
144
                vlc_cond_timedwait( &worker->head.worker_wait, &worker->lock,
145 146
                                     worker->head.deadline );
            }
147
            vlc_mutex_unlock( &worker->lock );
148 149 150 151 152 153 154 155
        }
    }

    return NULL;
}

static void BackgroundWorkerCancel( struct background_worker* worker, void* id)
{
156
    vlc_mutex_lock( &worker->lock );
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
    for( size_t i = 0; i < vlc_array_count( &worker->tail.data ); )
    {
        struct bg_queued_item* item =
            vlc_array_item_at_index( &worker->tail.data, i );

        if( id == NULL || item->id == id )
        {
            vlc_array_remove( &worker->tail.data, i );
            worker->conf.pf_release( item->entity );
            free( item );
            continue;
        }

        ++i;
    }

    while( ( id == NULL && worker->head.active )
        || ( id != NULL && worker->head.id == id ) )
    {
        worker->head.deadline = VLC_TS_0;
177
        vlc_cond_signal( &worker->head.worker_wait );
178
        vlc_cond_signal( &worker->tail.wait );
179
        vlc_cond_wait( &worker->head.wait, &worker->lock );
180
    }
181
    vlc_mutex_unlock( &worker->lock );
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
}

struct background_worker* background_worker_New( void* owner,
    struct background_worker_config* conf )
{
    struct background_worker* worker = malloc( sizeof *worker );

    if( unlikely( !worker ) )
        return NULL;

    worker->conf = *conf;
    worker->owner = owner;
    worker->head.id = NULL;
    worker->head.active = false;
    worker->head.deadline = VLC_TS_INVALID;

198
    vlc_mutex_init( &worker->lock );
199
    vlc_cond_init( &worker->head.wait );
200
    vlc_cond_init( &worker->head.worker_wait );
201 202

    vlc_array_init( &worker->tail.data );
203
    vlc_cond_init( &worker->tail.wait );
204 205 206 207 208 209 210 211 212 213 214 215 216 217

    return worker;
}

int background_worker_Push( struct background_worker* worker, void* entity,
                        void* id, int timeout )
{
    struct bg_queued_item* item = malloc( sizeof( *item ) );

    if( unlikely( !item ) )
        return VLC_EGENERIC;

    item->id = id;
    item->entity = entity;
218
    item->timeout = timeout < 0 ? worker->conf.default_timeout : timeout;
219

220
    vlc_mutex_lock( &worker->lock );
221
    int i_ret = vlc_array_append( &worker->tail.data, item );
222
    vlc_cond_signal( &worker->tail.wait );
223 224 225 226 227
    if( i_ret != 0 )
    {
        free( item );
        return VLC_EGENERIC;
    }
228 229 230 231 232 233 234 235 236 237 238 239

    if( worker->head.active == false )
    {
        worker->head.probe_request = false;
        worker->head.active =
            !vlc_clone_detach( NULL, Thread, worker, VLC_THREAD_PRIORITY_LOW );
    }

    if( worker->head.active )
        worker->conf.pf_hold( item->entity );

    int ret = worker->head.active ? VLC_SUCCESS : VLC_EGENERIC;
240
    vlc_mutex_unlock( &worker->lock );
241 242 243 244 245 246 247 248 249 250 251

    return ret;
}

void background_worker_Cancel( struct background_worker* worker, void* id )
{
    BackgroundWorkerCancel( worker, id );
}

void background_worker_RequestProbe( struct background_worker* worker )
{
252
    vlc_mutex_lock( &worker->lock );
253
    worker->head.probe_request = true;
254
    vlc_cond_signal( &worker->head.worker_wait );
255
    vlc_mutex_unlock( &worker->lock );
256 257 258 259 260 261
}

void background_worker_Delete( struct background_worker* worker )
{
    BackgroundWorkerCancel( worker, NULL );
    vlc_array_clear( &worker->tail.data );
262 263
    vlc_mutex_destroy( &worker->lock );
    vlc_cond_destroy( &worker->head.wait );
264
    vlc_cond_destroy( &worker->head.worker_wait );
265
    vlc_cond_destroy( &worker->tail.wait );
266 267
    free( worker );
}