background_worker.c 6.61 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 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
/*****************************************************************************
 * 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

#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;

40
    vlc_mutex_t lock; /**< acquire to inspect members that follow */
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    struct {
        bool probe_request; /**< true if a probe is requested */
        vlc_cond_t wait; /**< wait for update in terms of head */
        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 {
        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;

63
        vlc_mutex_lock( &worker->lock );
64 65 66 67 68 69 70 71 72
        {
            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 );
            }

73 74 75
            worker->head.deadline = INT64_MAX;
            worker->head.active = item != NULL;
            worker->head.id = item ? item->id : NULL;
76

77 78
            if( item && item->timeout > 0 )
                worker->head.deadline = mdate() + item->timeout * 1000;
79 80
            vlc_cond_broadcast( &worker->head.wait );
        }
81
        vlc_mutex_unlock( &worker->lock );
82 83 84 85 86 87 88 89 90 91 92 93 94

        if( item == NULL )
            break;

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

        for( ;; )
        {
95
            vlc_mutex_lock( &worker->lock );
96 97 98 99

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

100
            vlc_mutex_unlock( &worker->lock );
101 102 103 104 105 106 107 108 109 110

            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;
            }

111
            vlc_mutex_lock( &worker->lock );
112 113 114
            if( worker->head.probe_request == false &&
                worker->head.deadline > mdate() )
            {
115
                vlc_cond_timedwait( &worker->head.wait, &worker->lock,
116 117
                                     worker->head.deadline );
            }
118
            vlc_mutex_unlock( &worker->lock );
119 120 121 122 123 124 125 126
        }
    }

    return NULL;
}

static void BackgroundWorkerCancel( struct background_worker* worker, void* id)
{
127
    vlc_mutex_lock( &worker->lock );
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
    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;
        vlc_cond_broadcast( &worker->head.wait );
149
        vlc_cond_wait( &worker->head.wait, &worker->lock );
150
    }
151
    vlc_mutex_unlock( &worker->lock );
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
}

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;

168
    vlc_mutex_init( &worker->lock );
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
    vlc_cond_init( &worker->head.wait );

    vlc_array_init( &worker->tail.data );

    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;
186
    item->timeout = timeout < 0 ? worker->conf.default_timeout : timeout;
187

188
    vlc_mutex_lock( &worker->lock );
189 190 191 192 193 194 195 196 197 198 199 200 201
    vlc_array_append( &worker->tail.data, item );

    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;
202
    vlc_mutex_unlock( &worker->lock );
203 204 205 206 207 208 209 210 211 212 213

    return ret;
}

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

void background_worker_RequestProbe( struct background_worker* worker )
{
214
    vlc_mutex_lock( &worker->lock );
215 216
    worker->head.probe_request = true;
    vlc_cond_broadcast( &worker->head.wait );
217
    vlc_mutex_unlock( &worker->lock );
218 219 220 221 222 223
}

void background_worker_Delete( struct background_worker* worker )
{
    BackgroundWorkerCancel( worker, NULL );
    vlc_array_clear( &worker->tail.data );
224 225
    vlc_mutex_destroy( &worker->lock );
    vlc_cond_destroy( &worker->head.wait );
226 227
    free( worker );
}