/* * %CopyrightBegin% * * Copyright Ericsson AB 2011-2016. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * %CopyrightEnd% */ #include #include #include #include "erl_driver.h" #define BLOCKS_PER_THREAD 100000 #define NO_THREADS 10 #define BLOCKS_PER_CTRL 1000 typedef struct { ErlDrvMutex *mtx; ErlDrvCond *cnd; int b; int *go; int *skip; void *blocks[BLOCKS_PER_THREAD]; } test_thread_data; typedef struct { ErlDrvPort port; int b; int go; int skip; test_thread_data ttd[NO_THREADS+1]; ErlDrvTid tids[NO_THREADS+1]; } test_data; static ErlDrvData start(ErlDrvPort port, char *command); static void stop(ErlDrvData data); static ErlDrvSSizeT control(ErlDrvData drv_data, unsigned int command, char *buf, ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen); static ErlDrvEntry thr_free_drv_entry = { NULL /* init */, start, stop, NULL /* output */, NULL /* ready_input */, NULL /* ready_output */, "thr_free_drv", NULL /* finish */, NULL /* handle */, control, NULL /* timeout */, NULL /* outputv */, NULL /* ready_async */, NULL /* flush */, NULL /* call */, NULL /* event */, ERL_DRV_EXTENDED_MARKER, ERL_DRV_EXTENDED_MAJOR_VERSION, ERL_DRV_EXTENDED_MINOR_VERSION, ERL_DRV_FLAG_USE_PORT_LOCKING, NULL /* handle2 */, NULL /* handle_monitor */ }; DRIVER_INIT(thr_free_drv) { return &thr_free_drv_entry; } void * test_thread(void *vttd) { test_thread_data *ttd = (test_thread_data *) vttd; int i, skip; erl_drv_mutex_lock(ttd->mtx); while (!*ttd->go) erl_drv_cond_wait(ttd->cnd, ttd->mtx); skip = *ttd->skip; erl_drv_mutex_unlock(ttd->mtx); if (!skip) { for (i = 0; i < BLOCKS_PER_THREAD; i++) driver_free(ttd->blocks[i]); } return NULL; } ErlDrvData start(ErlDrvPort port, char *command) { int join = 0, t, b, res; test_thread_data *ttd; test_data *td = driver_alloc(sizeof(test_data)); if (!td) return ERL_DRV_ERROR_GENERAL; ttd = td->ttd; for (b = 0; b < BLOCKS_PER_THREAD; b++) for (t = 0; t <= NO_THREADS; t++) ttd[t].blocks[b] = NULL; ttd[0].mtx = NULL; ttd[0].cnd = NULL; for (b = 0; b < BLOCKS_PER_THREAD; b++) { for (t = 0; t <= NO_THREADS; t++) { ttd[t].blocks[b] = driver_alloc(1); if (ttd[t].blocks[b] == NULL) goto fail; } } td->b = -1; td->go = 0; td->skip = 0; ttd[0].mtx = erl_drv_mutex_create("test_mutex"); if (!ttd[0].mtx) goto fail; ttd[0].cnd = erl_drv_cond_create("test_cnd"); if (!ttd[0].cnd) goto fail; ttd[0].go = &td->go; ttd[0].skip = &td->skip; for (t = 1; t <= NO_THREADS; t++) { ttd[t].mtx = ttd[0].mtx; ttd[t].cnd = ttd[0].cnd; ttd[t].go = ttd[0].go; ttd[t].skip = ttd[0].skip; res = erl_drv_thread_create("test_thread", &td->tids[t], test_thread, &ttd[t], NULL); if (res != 0) goto fail; join = t; } td->port = port; return (ErlDrvData) td; fail: if (join) { erl_drv_mutex_lock(ttd[0].mtx); td->go = 1; td->skip = 1; erl_drv_cond_broadcast(ttd[0].cnd); erl_drv_mutex_unlock(ttd[0].mtx); for (t = 1; t <= join; t++) erl_drv_thread_join(td->tids[t], NULL); } if (ttd[0].mtx) erl_drv_mutex_destroy(ttd[0].mtx); if (ttd[0].cnd) erl_drv_cond_destroy(ttd[0].cnd); for (b = 0; b < BLOCKS_PER_THREAD; b++) { for (t = 0; t <= NO_THREADS; t++) { if (ttd[t].blocks[b] != NULL) driver_free(ttd[t].blocks[b]); } } driver_free(td); return ERL_DRV_ERROR_GENERAL; } static void stop(ErlDrvData drv_data) { test_data *td = (test_data *) drv_data; int t, b; for (t = 1; t <= NO_THREADS; t++) erl_drv_thread_join(td->tids[t], NULL); for (b = 0; b < BLOCKS_PER_THREAD; b++) { if (td->ttd[0].blocks[b]) driver_free(td->ttd[0].blocks[b]); } erl_drv_mutex_destroy(td->ttd[0].mtx); erl_drv_cond_destroy(td->ttd[0].cnd); driver_free(td); } static ErlDrvSSizeT control(ErlDrvData drv_data, unsigned int command, char *buf, ErlDrvSizeT len, char **rbuf, ErlDrvSizeT rlen) { test_data *td = (test_data *) drv_data; char *result = "failure"; int i, b; int res; ErlDrvSSizeT result_len; if (td->b == -1) { erl_drv_mutex_lock(td->ttd[0].mtx); td->go = 1; erl_drv_cond_broadcast(td->ttd[0].cnd); erl_drv_mutex_unlock(td->ttd[0].mtx); td->b = 0; } for (i = 0, b = td->b; i < BLOCKS_PER_CTRL && b < BLOCKS_PER_THREAD; i++, b++) { driver_free(td->ttd[0].blocks[b]); td->ttd[0].blocks[b] = NULL; } td->b = b; if (b >= BLOCKS_PER_THREAD) result = "done"; else result = "more"; result_len = strlen(result); if (result_len <= rlen) { memcpy(*rbuf, result, result_len); return result_len; } else { *rbuf = driver_alloc(result_len); if (!*rbuf) { driver_failure_posix(td->port, ENOMEM); return 0; } else { memcpy(*rbuf, result, result_len); return result_len; } } }