//scheduler.c: /* * Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2012 * * This file is part of libroar a part of RoarAudio, * a cross-platform sound system for both, home and professional use. * See README for details. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 * as published by the Free Software Foundation. * * libroar 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * NOTE for everyone want's to change something and send patches: * read README and HACKING! There a addition information on * the license of this document you need to read before you send * any patches. * * NOTE for uses of non-GPL (LGPL,...) software using libesd, libartsc * or libpulse*: * The libs libroaresd, libroararts and libroarpulse link this lib * and are therefore GPL. Because of this it may be illigal to use * them with any software that uses libesd, libartsc or libpulse*. */ #include "libroar.h" #define INIT_SIZE 8 #define MAX_PROTOS 16 struct protocol { const struct roar_keyval * para; ssize_t paralen; struct roar_dl_lhandle * lhandle; const struct roar_dl_proto * impl; }; struct roar_scheduler { size_t refc; int flags; enum roar_scheduler_strategy strategy; struct roar_scheduler_source ** sources; size_t sources_len; struct roar_vio_select * vios; size_t vios_len; struct roar_dl_fnreg callback; struct protocol protos[MAX_PROTOS]; }; static int __update_cpi_listen_client (struct roar_scheduler * sched, struct roar_scheduler_source * source); struct roar_scheduler * roar_scheduler_new(int flags, enum roar_scheduler_strategy strategy) { struct roar_scheduler * sched = roar_mm_malloc(sizeof(struct roar_scheduler)); if ( sched == NULL ) return NULL; if ( flags == ROAR_SCHEDULER_FLAG_DEFAULT ) flags = ROAR_SCHEDULER_FLAG_NONE; memset(sched, 0, sizeof(struct roar_scheduler)); sched->refc = 1; sched->flags = flags; sched->strategy = strategy; sched->sources = roar_mm_malloc(INIT_SIZE*sizeof(struct roar_scheduler_source *)); if ( sched->sources != NULL ) { sched->sources_len = INIT_SIZE; memset(sched->sources, 0, INIT_SIZE*sizeof(struct roar_scheduler_source *)); } sched->vios = roar_mm_malloc(sched->sources_len*sizeof(struct roar_vio_select)); if ( sched->vios != 0 ) sched->vios_len = sched->sources_len; return sched; } #define _CHKSCHED(extra) if ( sched == NULL || (extra) ) { roar_err_set(ROAR_ERROR_FAULT); return -1; } int roar_scheduler_ref(struct roar_scheduler * sched) { _CHKSCHED(0); sched->refc++; return 0; } int roar_scheduler_unref(struct roar_scheduler * sched) { size_t i; _CHKSCHED(0); sched->refc--; if (sched->refc) return 0; for (i = 0; i < sched->sources_len; i++) if ( sched->sources[i] != NULL ) roar_scheduler_source_del(sched, sched->sources[i]); if ( sched->vios != NULL ) roar_mm_free(sched->vios); if ( sched->sources != NULL ) roar_mm_free(sched->sources); roar_mm_free(sched); return 0; } static void __delete_cpi_client(struct roar_scheduler * sched, struct roar_scheduler_source * cur, struct roar_dl_librarypara * para) { if ( cur->handle.cpi.impl->unset_proto != NULL ) cur->handle.cpi.impl->unset_proto(cur->handle.cpi.client, cur->vio, &(cur->handle.cpi.obuffer), &(cur->handle.cpi.userdata), cur->handle.cpi.protopara, cur->handle.cpi.protoparalen, para); roar_vio_close(cur->vio); cur->vio = NULL; if ( cur->handle.cpi.obuffer != NULL ) { roar_buffer_free(cur->handle.cpi.obuffer); cur->handle.cpi.obuffer = NULL; } if ( cur->handle.cpi.userdata != NULL ) { roar_mm_free(cur->handle.cpi.userdata); cur->handle.cpi.userdata = NULL; } roar_scheduler_source_del(sched, cur); } static int __flush_cpi_client(struct roar_scheduler * sched, struct roar_scheduler_source * cur, struct roar_dl_librarypara * para) { size_t len; ssize_t ret; void * buf; if ( roar_buffer_get_len(cur->handle.cpi.obuffer, &len) == -1 ) return 0; if ( roar_buffer_get_data(cur->handle.cpi.obuffer, &buf) == -1 ) return 0; if ( len != 0 ) { ret = roar_vio_write(cur->vio, buf, len); if ( ret < 1 ) return -1; } else { ret = len; } if ( ret == (ssize_t)len ) { if ( roar_buffer_next(&(cur->handle.cpi.obuffer)) == -1 ) return -1; } else { if ( roar_buffer_set_offset(cur->handle.cpi.obuffer, ret) == -1 ) return -1; } return 0; } static int __run_waits(struct roar_scheduler * sched) { struct roar_scheduler_source * cur; size_t i; int ret = -1; for (i = 0; ret == -1 && i < sched->sources_len; i++) { if ( (cur = sched->sources[i]) == NULL ) continue; switch (cur->type) { case ROAR_SCHEDULER_PLUGIN: ret = roar_dl_appsched_trigger(cur->lhandle, ROAR_DL_APPSCHED_WAIT); break; case ROAR_SCHEDULER_PLUGINCONTAINER: ret = roar_plugincontainer_appsched_trigger(cur->handle.container, ROAR_DL_APPSCHED_WAIT); break; #ifndef DEBUG default: /* noop */ break; #endif } } return ret; } // what to do?: // 0. get all VIOs. // 1. get timeout or use internal default. // 2. run roar_vio_select(). // 3. send all events based on results. // 4. send UPDATE to all plugins and containers. int roar_scheduler_iterate(struct roar_scheduler * sched) { enum roar_scheduler_strategy strategy; struct roar_vio_selecttv timeout = {8, 0}; // default: 8 sec. Just a random value. struct roar_scheduler_source * cur, * new_client; struct roar_dl_librarypara * para; struct roar_dl_lhandle * lhandle; ssize_t ret; size_t i; int have_timeout = 0; size_t todo = 0, vios = 0; int tmp; _CHKSCHED(0); strategy = sched->strategy; if ( strategy == ROAR_SCHEDULER_STRATEGY_DEFAULT ) strategy = ROAR_SCHEDULER_STRATEGY_SELECTORWAIT; if ( sched->vios == NULL || sched->vios_len < sched->sources_len ) { if ( sched->vios != NULL ) { roar_mm_free(sched->vios); sched->vios = NULL; sched->vios_len = 0; } sched->vios = roar_mm_malloc(sched->sources_len*sizeof(struct roar_vio_select)); if ( sched->vios != NULL ) sched->vios_len = sched->sources_len; } // error from roar_mm_malloc() is still set. if ( sched->vios == NULL ) return -1; memset(sched->vios, 0, sched->vios_len*sizeof(struct roar_vio_select)); for (i = 0; i < sched->vios_len; i++) sched->vios[i].eventsq = ROAR_VIO_SELECT_NO_RETEST; for (i = 0; i < sched->sources_len; i++) { if ( (cur = sched->sources[i]) == NULL ) continue; switch (cur->type) { case ROAR_SCHEDULER_VIO: ROAR_VIO_SELECT_SETVIO(&(sched->vios[i]), cur->vio, cur->handle.eventsq); todo++; vios++; break; case ROAR_SCHEDULER_TIMEOUT: timeout = cur->handle.timeout; have_timeout = 1; todo++; break; case ROAR_SCHEDULER_CPI_LISTEN: if ( cur->flags & ROAR_SCHEDULER_FLAG_STUB ) __update_cpi_listen_client(sched, cur); if ( !(cur->flags & ROAR_SCHEDULER_FLAG_STUB) ) { ROAR_VIO_SELECT_SETVIO(&(sched->vios[i]), cur->vio, ROAR_VIO_SELECT_READ); todo++; vios++; } break; case ROAR_SCHEDULER_CPI_CLIENT: if ( cur->flags & ROAR_SCHEDULER_FLAG_STUB ) __update_cpi_listen_client(sched, cur); if ( !(cur->flags & ROAR_SCHEDULER_FLAG_STUB) ) { tmp = 0; if ( cur->handle.cpi.impl->status != NULL ) { if ( cur->lhandle != NULL ) roar_dl_context_restore(cur->lhandle); para = roar_dl_getpara(cur->lhandle); if ( cur->handle.cpi.impl->status(cur->handle.cpi.client, cur->vio, &(cur->handle.cpi.obuffer), &(cur->handle.cpi.userdata), cur->handle.cpi.protopara, cur->handle.cpi.protoparalen, para) & ROAR_DL_PROTO_STATUS_RX_READY ) tmp |= ROAR_VIO_SELECT_READ; if ( para != NULL ) roar_dl_para_unref(para); if ( cur->lhandle != NULL ) roar_dl_context_store(cur->lhandle); } else { tmp |= ROAR_VIO_SELECT_READ; } if ( sched->sources[i]->handle.cpi.obuffer != NULL ) tmp |= ROAR_VIO_SELECT_WRITE; ROAR_VIO_SELECT_SETVIO(&(sched->vios[i]), sched->sources[i]->vio, tmp); todo++; vios++; } break; case ROAR_SCHEDULER_PLUGIN: case ROAR_SCHEDULER_PLUGINCONTAINER: todo++; break; #ifndef DEBUG default: /* noop */ break; #endif } } if (!todo) { roar_err_set(ROAR_ERROR_NOENT); return 0; } if ( strategy == ROAR_SCHEDULER_STRATEGY_WAITORSELECT || strategy == ROAR_SCHEDULER_STRATEGY_WAIT ) { ret = __run_waits(sched); if ( ret == 0 || strategy == ROAR_SCHEDULER_STRATEGY_WAIT ) ret = 1; } else { ret = -1; } if ( ret == -1 && !(strategy == ROAR_SCHEDULER_STRATEGY_SELECTORWAIT && vios == 0) ) { ret = roar_vio_select(sched->vios, sched->vios_len, &timeout, NULL); } if ( ret < 0 && strategy == ROAR_SCHEDULER_STRATEGY_SELECTORWAIT ) { ret = __run_waits(sched); if ( ret == 0 ) ret = 1; if ( ret == -1 && vios == 0 ) { if ( sched->flags & ROAR_SCHEDULER_FLAG_KEEP_RUNNING ) { ret = roar_vio_select(sched->vios, sched->vios_len, &timeout, NULL); } else { return 0; } } } if ( ret == -1 ) return -1; if ( ret == 0 && !have_timeout ) return 1; for (i = 0; i < sched->sources_len; i++) { if ( (cur = sched->sources[i]) == NULL ) continue; switch (cur->type) { case ROAR_SCHEDULER_VIO: if ( sched->vios[i].eventsa ) if ( cur->cb != NULL ) cur->cb(sched->sources[i], sched->sources[i]->userdata, sched->vios[i].eventsa); break; case ROAR_SCHEDULER_TIMEOUT: if ( ret == 0 ) if ( cur->cb != NULL ) cur->cb(sched->sources[i], sched->sources[i]->userdata, 0); break; case ROAR_SCHEDULER_PLUGIN: roar_dl_appsched_trigger(cur->lhandle, ROAR_DL_APPSCHED_UPDATE); break; case ROAR_SCHEDULER_PLUGINCONTAINER: roar_plugincontainer_appsched_trigger(cur->handle.container, ROAR_DL_APPSCHED_UPDATE); break; case ROAR_SCHEDULER_CPI_LISTEN: if ( !sched->vios[i].eventsa ) continue; if ( cur->flags & ROAR_SCHEDULER_FLAG_STUB ) continue; if ( cur->cb != NULL ) cur->cb(sched->sources[i], sched->sources[i]->userdata, sched->vios[i].eventsa); new_client = roar_mm_malloc(sizeof(struct roar_scheduler_source)); if ( new_client == NULL ) continue; memcpy(new_client, cur, sizeof(struct roar_scheduler_source)); new_client->type = ROAR_SCHEDULER_CPI_CLIENT; new_client->flags = ROAR_SCHEDULER_FLAG_FREE; new_client->vio = roar_mm_malloc(sizeof(struct roar_vio_calls)); if ( new_client->vio == NULL ) { roar_mm_free(new_client); continue; } if ( roar_vio_accept(new_client->vio, cur->vio) == -1 ) { roar_mm_free(new_client->vio); roar_mm_free(new_client); continue; } new_client->vio->flags |= ROAR_VIO_FLAGS_FREESELF; if ( roar_scheduler_source_add(sched, new_client) == -1 ) { roar_vio_close(new_client->vio); roar_mm_free(new_client); } roar_vio_unref(new_client->vio); if ( new_client->cb != NULL ) new_client->cb(new_client, new_client->userdata, 0); if ( cur->handle.cpi.impl->set_proto != NULL ) { lhandle = new_client->lhandle; para = roar_dl_getpara(lhandle); if ( lhandle != NULL ) roar_dl_context_restore(lhandle); new_client->handle.cpi.impl->set_proto(new_client->handle.cpi.client, new_client->vio, &(new_client->handle.cpi.obuffer), &(new_client->handle.cpi.userdata), new_client->handle.cpi.protopara, new_client->handle.cpi.protoparalen, para); if ( lhandle != NULL ) roar_dl_context_store(lhandle); if ( para != NULL ) roar_dl_para_unref(para); } break; case ROAR_SCHEDULER_CPI_CLIENT: if ( !sched->vios[i].eventsa ) continue; if ( cur->flags & ROAR_SCHEDULER_FLAG_STUB ) continue; if ( cur->cb != NULL ) cur->cb(sched->sources[i], sched->sources[i]->userdata, sched->vios[i].eventsa); lhandle = cur->lhandle; para = roar_dl_getpara(lhandle); tmp = 0; if ( sched->vios[i].eventsa & ROAR_VIO_SELECT_WRITE ) { if ( cur->handle.cpi.impl->flush != NULL ) { if ( lhandle != NULL ) roar_dl_context_restore(lhandle); cur->handle.cpi.impl->flush(cur->handle.cpi.client, cur->vio, &(cur->handle.cpi.obuffer), &(cur->handle.cpi.userdata), cur->handle.cpi.protopara, cur->handle.cpi.protoparalen, para); if ( lhandle != NULL ) roar_dl_context_store(lhandle); } else { tmp = __flush_cpi_client(sched, cur, para); } if ( tmp == 0 && cur->handle.cpi.obuffer == NULL && cur->handle.cpi.impl->flushed != NULL ) { if ( lhandle != NULL ) roar_dl_context_restore(lhandle); if ( cur->handle.cpi.impl->flushed(cur->handle.cpi.client, cur->vio, &(cur->handle.cpi.obuffer), &(cur->handle.cpi.userdata), cur->handle.cpi.protopara, cur->handle.cpi.protoparalen, para) == -1 ) tmp = -1; if ( lhandle != NULL ) roar_dl_context_store(lhandle); } } if ( sched->vios[i].eventsa & ROAR_VIO_SELECT_READ ) { if ( cur->handle.cpi.impl->handle != NULL ) { if ( lhandle != NULL ) roar_dl_context_restore(lhandle); if ( cur->handle.cpi.impl->handle(cur->handle.cpi.client, cur->vio, &(cur->handle.cpi.obuffer), &(cur->handle.cpi.userdata), cur->handle.cpi.protopara, cur->handle.cpi.protoparalen, para) == -1 ) { tmp = -1; } if ( lhandle != NULL ) roar_dl_context_store(lhandle); } } if ( tmp == -1 ) { if ( lhandle != NULL ) roar_dl_context_restore(lhandle); __delete_cpi_client(sched, cur, para); if ( lhandle != NULL ) roar_dl_context_store(lhandle); } if ( para != NULL ) roar_dl_para_unref(para); break; #ifndef DEBUG default: /* noop */ break; #endif } } return 1; } int roar_scheduler_run(struct roar_scheduler * sched) { int ret; _CHKSCHED(0); while ((ret = roar_scheduler_iterate(sched)) > 0); return ret; } static int __cpi_callback(enum roar_dl_fnreg_action action, int fn, int subtype, const void * object, size_t objectlen, int version, int options, void * userdata, struct roar_dl_lhandle * lhandle) { const struct roar_dl_proto * impl = object; struct roar_scheduler * sched = userdata; struct roar_dl_librarypara * para; size_t i; (void)fn, (void)subtype, (void)version, (void)options; if ( sched == NULL ) { roar_err_set(ROAR_ERROR_FAULT); return -1; } if ( objectlen != ROAR_DL_PROTO_SIZE ) { ROAR_WARN("__cpi_callback(*): Library %p tries to register protocol with bad object length.", lhandle); roar_err_set(ROAR_ERROR_BADLIB); return -1; } switch (action) { case ROAR_DL_FNREG: for (i = 0; i < MAX_PROTOS; i++) { if ( sched->protos[i].impl == NULL ) { memset(&(sched->protos[i]), 0, sizeof(sched->protos[i])); sched->protos[i].para = NULL; sched->protos[i].paralen = -1; sched->protos[i].lhandle = lhandle; sched->protos[i].impl = impl; return 0; } } break; case ROAR_DL_FNUNREG: for (i = 0; i < sched->sources_len; i++) { if ( sched->sources[i] == NULL ) continue; if ( sched->sources[i]->type != ROAR_SCHEDULER_CPI_LISTEN && sched->sources[i]->type != ROAR_SCHEDULER_CPI_CLIENT ) continue; if ( sched->sources[i]->handle.cpi.proto != impl->proto ) continue; para = roar_dl_getpara(lhandle); if ( lhandle != NULL ) roar_dl_context_restore(lhandle); __delete_cpi_client(sched, sched->sources[i], para); if ( lhandle != NULL ) roar_dl_context_store(lhandle); if ( para != NULL ) roar_dl_para_unref(para); } for (i = 0; i < MAX_PROTOS; i++) { if ( sched->protos[i].impl != NULL && sched->protos[i].lhandle == lhandle ) { memset(&(sched->protos[i]), 0, sizeof(sched->protos[i])); sched->protos[i].impl = NULL; } } return 0; break; } roar_err_set(ROAR_ERROR_NOSPC); return -1; } static int __update_cpi_service (struct roar_scheduler * sched, struct roar_scheduler_source * source, int del) { if ( del ) { return roar_dl_unregister_fn2(ROAR_DL_HANDLE_LIBROAR, ROAR_DL_FN_REGFN, ROAR_DL_FNREG_SUBTYPE, &(sched->callback), sizeof(sched->callback), ROAR_DL_FNREG_VERSION, ROAR_DL_FNREG_OPT_NONE); } sched->callback.fn = ROAR_DL_FN_PROTO; sched->callback.subtype = ROAR_DL_PROTO_SUBTYPE; sched->callback.version = ROAR_DL_PROTO_VERSION; sched->callback.callback = __cpi_callback; sched->callback.userdata = sched; ROAR_DL_RFNREG(ROAR_DL_HANDLE_LIBROAR, sched->callback); return -1; } static int __update_cpi_listen_client (struct roar_scheduler * sched, struct roar_scheduler_source * source) { size_t i; ROAR_DBG("__update_cpi_listen_client(sched=%p, source=%p): proto=%i, impl=%p", sched, source, source->handle.cpi.proto, source->handle.cpi.impl); if ( source->handle.cpi.proto < 1 && source->handle.cpi.impl != NULL ) source->handle.cpi.proto = source->handle.cpi.impl->proto; if ( source->handle.cpi.proto > 0 && source->handle.cpi.impl == NULL ) { for (i = 0; i < MAX_PROTOS; i++) { if ( sched->protos[i].impl == NULL ) continue; ROAR_DBG("__update_cpi_listen_client(sched=%p, source=%p): proto=%i<->%i", sched, source, sched->protos[i].impl->proto, source->handle.cpi.proto); if ( sched->protos[i].impl->proto != source->handle.cpi.proto ) continue; source->handle.cpi.impl = sched->protos[i].impl; if ( source->lhandle == NULL && sched->protos[i].lhandle != NULL ) if ( roar_dl_ref(sched->protos[i].lhandle) == 0 ) source->lhandle = sched->protos[i].lhandle; break; } } if ( source->handle.cpi.proto > 0 ) { source->flags |= ROAR_SCHEDULER_FLAG_STUB; if ( source->handle.cpi.impl == NULL ) return 0; source->flags -= ROAR_SCHEDULER_FLAG_STUB; return 0; } roar_err_set(ROAR_ERROR_INVAL); return -1; } int roar_scheduler_source_add(struct roar_scheduler * sched, struct roar_scheduler_source * source) { size_t i; struct roar_scheduler_source ** next = NULL; int err; _CHKSCHED(source == NULL); ROAR_DBG("roar_scheduler_source_add(sched=%p, source=%p): proto=%i, impl=%p", sched, source, source->handle.cpi.proto, source->handle.cpi.impl); for (i = 0; i < sched->sources_len; i++) { if ( sched->sources[i] != NULL ) continue; next = &(sched->sources[i]); break; } if ( next == NULL ) { // TODO: re-allocate some space here. roar_err_set(ROAR_ERROR_NOSPC); return -1; } if ( source->flags == ROAR_SCHEDULER_FLAG_DEFAULT ) source->flags = ROAR_SCHEDULER_FLAG_NONE; switch (source->type) { case ROAR_SCHEDULER_CPI_LISTEN: case ROAR_SCHEDULER_CPI_CLIENT: if ( __update_cpi_listen_client(sched, source) == -1 ) return -1; break; case ROAR_SCHEDULER_CPI_SERVICE: if ( __update_cpi_service(sched, source, 0) == -1 ) return -1; break; #ifndef DEBUG default: /* noop */ break; #endif } if ( source->lhandle != NULL ) if ( roar_dl_ref(source->lhandle) == -1 ) return -1; if ( source->vio != NULL ) { if ( roar_vio_ref(source->vio) == -1 ) { err = roar_error; if ( source->lhandle != NULL ) roar_dl_unref(source->lhandle); roar_err_set(err); return -1; } } if ( source->type == ROAR_SCHEDULER_PLUGINCONTAINER ) { if ( roar_plugincontainer_ref(source->handle.container) == -1 ) { err = roar_error; if ( source->lhandle != NULL ) roar_dl_unref(source->lhandle); if ( source->vio != NULL ) roar_vio_unref(source->vio); roar_err_set(err); return -1; } } *next = source; return 0; } int roar_scheduler_source_del(struct roar_scheduler * sched, struct roar_scheduler_source * source) { size_t i; struct roar_scheduler_source ** next = NULL; _CHKSCHED(source == NULL); for (i = 0; i < sched->sources_len; i++) { if ( sched->sources[i] != source ) continue; next = &(sched->sources[i]); break; } if ( next == NULL ) { roar_err_set(ROAR_ERROR_NOENT); return -1; } switch (source->type) { case ROAR_SCHEDULER_PLUGINCONTAINER: roar_plugincontainer_ref(source->handle.container); break; case ROAR_SCHEDULER_CPI_SERVICE: if ( __update_cpi_service(sched, source, 1) == -1 ) return -1; break; #ifndef DEBUG default: /* noop */ break; #endif } if ( source->lhandle != NULL ) roar_dl_unref(source->lhandle); if ( source->vio != NULL ) roar_vio_unref(source->vio); if ( source->flags & ROAR_SCHEDULER_FLAG_FREE ) roar_mm_free(source); *next = NULL; return 0; } //ll