/* * Send notifications from this thread, to be caught in the main thread */ static gpointer thread1_start (ITSignaler *its) { gint i, counter; itsignaler_ref (its); for (counter = 0, i = 0; i < MAX_ITERATIONS; i++) { Data *data; g_mutex_lock (&mutex); data = g_new0 (Data, 1); data->counter = counter++; #ifndef PERF g_print ("Pushing %d...\n", counter); #endif itsignaler_push_notification (its, data, g_free); g_mutex_unlock (&mutex); g_mutex_lock (&mutex); data = g_new0 (Data, 1); data->counter = counter++; itsignaler_push_notification (its, data, g_free); g_mutex_unlock (&mutex); } itsignaler_unref (its); return (gpointer) 0x01; }
void _gda_worker_unref (GdaWorker *worker, gboolean give_to_bg) { g_assert (worker); gboolean unique_locked = FALSE; if (worker->location) { g_mutex_lock (&unique_worker_mutex); unique_locked = TRUE; } #ifdef DEBUG_NOTIFICATION g_print ("[W] GdaWorker %p reference decreased to ", worker); #endif g_rec_mutex_lock (& worker->rmutex); worker->ref_count --; #ifdef DEBUG_NOTIFICATION g_print ("%u\n", worker->ref_count); #endif if (worker->ref_count == 0) { /* destroy all the interal resources which will not be reused even if the GdaWorker is reused */ g_hash_table_destroy (worker->callbacks_hash); worker->callbacks_hash = NULL; g_hash_table_destroy (worker->jobs_hash); worker->jobs_hash = NULL; if (worker->location) *(worker->location) = NULL; if (give_to_bg) { /* re-create the resources so the GdaWorker is ready to be used again */ worker->ref_count = 1; worker->callbacks_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) declared_callback_free); worker->jobs_hash = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, (GDestroyNotify) worker_job_free); worker->worker_must_quit = 0; worker->location = NULL; bg_set_spare_gda_worker (worker); } else { /* REM: we don't need to g_thread_join() the worker thread because the "background" will do it */ /* free all the resources used by @worker */ itsignaler_unref (worker->submit_its); worker->worker_must_quit = 1; /* request the worker thread to exit */ } g_rec_mutex_unlock (& worker->rmutex); } else g_rec_mutex_unlock (& worker->rmutex); if (unique_locked) g_mutex_unlock (&unique_worker_mutex); }
/** * gda_worker_wait_job: (skip) * @worker: a #GdaWorker object * @func: the function to call from the worker thread * @data: (allow-none): the data to pass to @func, or %NULL * @data_destroy_func: (allow-none): a function to destroy @data, or %NULL * @error: (allow-none): a place to store errors, or %NULL. * * Request that the worker thread call @func with the @data argument, much like gda_worker_submit_job(), * but waits (blocks) until @func has been executed. * * Note: it's up to the caller to free the result, the #GdaWorker object will not do it (ownership of the result is * transfered to the caller). * * Returns: (transfer full): the result of @func's execution * * Since: 6.0 */ gpointer gda_worker_wait_job (GdaWorker *worker, GdaWorkerFunc func, gpointer data, GDestroyNotify data_destroy_func, GError **error) { g_return_val_if_fail (worker, NULL); g_return_val_if_fail (func, NULL); if (gda_worker_thread_is_worker (worker)) { /* we are called from within the worker thread => call the function directly */ gpointer retval; retval = func (data, error); if (data_destroy_func) data_destroy_func (data); return retval; } guint jid; /* prepare ITSignaler to be notified */ ITSignaler *its; its = itsignaler_new (); /* push job */ g_rec_mutex_lock (& worker->rmutex); /* required to call _gda_worker_submit_job_with_its() */ jid = _gda_worker_submit_job_with_its (worker, its, func, data, data_destroy_func, NULL, error); g_rec_mutex_unlock (& worker->rmutex); if (jid == 0) { /* an error occurred */ itsignaler_unref (its); return NULL; } WorkerJob *job; job = itsignaler_pop_notification (its, -1); g_assert (job); itsignaler_unref (its); gpointer result; g_assert (gda_worker_fetch_job_result (worker, jid, &result, error)); return result; }
static void declared_callback_free (DeclaredCallback *dc) { if (!dc) return; if (dc->source_sig_id) g_assert (itsignaler_remove (dc->its, dc->context, dc->source_sig_id)); if (dc->its) itsignaler_unref (dc->its); g_main_context_unref (dc->context); g_slice_free (DeclaredCallback, dc); }
/** * gda_worker_new: (skip) * * Creates a new #GdaWorker object. * * Returns: (transfer full): a new #GdaWorker, or %NULL if an error occurred * * Since: 6.0 */ GdaWorker * gda_worker_new (void) { GdaWorker *worker; worker = bg_get_spare_gda_worker (); if (worker) return worker; worker = g_slice_new0 (GdaWorker); worker->submit_its = itsignaler_new (); if (!worker->submit_its) { g_slice_free (GdaWorker, worker); return NULL; } worker->ref_count = 1; worker->callbacks_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) declared_callback_free); worker->jobs_hash = g_hash_table_new_full (g_int_hash, g_int_equal, NULL, (GDestroyNotify) worker_job_free); worker->worker_must_quit = 0; worker->location = NULL; g_rec_mutex_init (& worker->rmutex); gchar *str; static guint counter = 0; str = g_strdup_printf ("gdaWrkrTh%u", counter); counter++; worker->worker_thread = g_thread_try_new (str, (GThreadFunc) worker_thread_main, worker, NULL); g_free (str); if (!worker->worker_thread) { itsignaler_unref (worker->submit_its); g_hash_table_destroy (worker->callbacks_hash); g_hash_table_destroy (worker->jobs_hash); g_rec_mutex_clear (& worker->rmutex); g_slice_free (GdaWorker, worker); return NULL; } else bg_update_stats (BG_STARTED_THREADS); #ifdef DEBUG_NOTIFICATION g_print ("[W] created GdaWorker %p\n", worker); #endif bg_update_stats (BG_CREATED_WORKER); return worker; }
int test3 (void) { g_print ("Test3 started\n"); ITSignaler *its; its = itsignaler_new (); test3_destroyed = 0; itsignaler_push_notification (its, (gpointer) 0x01, test3_destroy_func); itsignaler_push_notification (its, (gpointer) 0x02, test3_destroy_func); itsignaler_push_notification (its, (gpointer) 0x03, test3_destroy_func); gpointer data; data = itsignaler_pop_notification (its, 100); if (!data || data != (gpointer) 0x01) { g_print ("Popped wrong value: %p instead of %p\n", data, (gpointer) 0x01); itsignaler_unref (its); return 1; } itsignaler_unref (its); return test3_destroyed == 2 ? 0 : 1; }
int test2 (void) { g_print ("Test2 started\n"); ITSignaler *its; its = itsignaler_new (); GTimer *timer; timer = g_timer_new (); GMainLoop *loop; loop = g_main_loop_new (NULL, FALSE); g_mutex_lock (&mutex); CbData cbdata; cbdata.counter = 0; cbdata.loop = loop; itsignaler_add (its, NULL, (ITSignalerFunc) source_callback, &cbdata, NULL); itsignaler_unref (its); g_mutex_unlock (&mutex); GThread *th; th = g_thread_new ("sub", (GThreadFunc) thread1_start, its); g_main_loop_run (loop); g_main_loop_unref (loop); g_timer_stop (timer); g_thread_join (th); if (cbdata.counter == MAX_ITERATIONS * 2) { gdouble duration; duration = g_timer_elapsed (timer, NULL); g_print ("Test Ok, %0.5f s\n", duration); g_timer_destroy (timer); return 0; } else { g_print ("Test Failed: got %d notification(s) out of %d\n", cbdata.counter, MAX_ITERATIONS * 2); g_timer_destroy (timer); return 1; } }
/* * main function of the worker thread */ static gpointer worker_thread_main (GdaWorker *worker) { #define TIMER 150 #ifdef DEBUG_NOTIFICATION g_print ("[W] GdaWorker %p, worker thread %p started!\n", worker, g_thread_self()); #endif itsignaler_ref (worker->submit_its); while (1) { WorkerJob *job; job = itsignaler_pop_notification (worker->submit_its, TIMER); if (job) { g_rec_mutex_lock (&worker->rmutex); if (! (job->status & JOB_CANCELLED)) { /* handle job */ job->status |= JOB_BEING_PROCESSED; g_rec_mutex_unlock (&worker->rmutex); job->result = job->func (job->data, & job->error); g_rec_mutex_lock (&worker->rmutex); job->status |= JOB_PROCESSED; if (job->reply_its) itsignaler_push_notification (job->reply_its, job, NULL); } if ((job->status & JOB_CANCELLED) && worker->jobs_hash) g_hash_table_remove (worker->jobs_hash, &job->id); g_rec_mutex_unlock (&worker->rmutex); } if (worker->worker_must_quit) { #ifdef DEBUG_NOTIFICATION g_print ("[W] GdaWorker %p, worker thread %p finished!\n", worker, g_thread_self()); #endif itsignaler_unref (worker->submit_its); g_rec_mutex_clear (& worker->rmutex); g_slice_free (GdaWorker, worker); bg_update_stats (BG_DESTROYED_WORKER); bg_join_thread (); return NULL; } } return NULL; }
static void worker_job_free (WorkerJob *job) { if (!job) return; if (job->data_destroy_func && job->data) job->data_destroy_func (job->data); if (job->result) { if (job->result_destroy_func) job->result_destroy_func (job->result); else g_warning (_("Possible memory leak as no destroy function provided to free value returned by the worker thread")); } if (job->error) g_error_free (job->error); if (job->reply_its) itsignaler_unref (job->reply_its); g_slice_free (WorkerJob, job); }
int test1 (void) { g_print ("Test1 started\n"); ITSignaler *its; its = itsignaler_new (); Data *data; #ifndef PERF g_print ("Try popping initially...\n"); #endif data = itsignaler_pop_notification (its, 0); if (data) { g_warning ("itsignaler_pop_notification() sould have returned NULL"); return EXIT_FAILURE; } #ifndef PERF g_print ("Done\n"); #endif GTimer *timer; timer = g_timer_new (); GMainLoop *loop; loop = g_main_loop_new (NULL, FALSE); GSource *source; source = itsignaler_create_source (its); itsignaler_unref (its); g_mutex_lock (&mutex); CbData cbdata; cbdata.its = its; cbdata.counter = 0; cbdata.loop = loop; g_source_set_callback (source, G_SOURCE_FUNC (source_callback), &cbdata, NULL); g_source_attach (source, NULL); g_mutex_unlock (&mutex); GThread *th; th = g_thread_new ("sub", (GThreadFunc) thread1_start, its); g_main_loop_run (loop); g_source_unref (source); g_timer_stop (timer); g_thread_join (th); if (cbdata.counter == MAX_ITERATIONS * 2) { gdouble duration; duration = g_timer_elapsed (timer, NULL); g_print ("Test Ok, %0.5f s\n", duration); g_timer_destroy (timer); return 0; } else { g_print ("Test Failed: got %d notification(s) out of %d\n", cbdata.counter, MAX_ITERATIONS * 2); g_timer_destroy (timer); return 1; } }
/** * gda_worker_do_job: (skip) * @worker: a #GdaWorker object * @context: (allow-none): a #GMainContext to execute a main loop in (while waiting), or %NULL * @timeout_ms: the maximum number of milisecons to wait before returning, or %0 for unlimited wait * @out_result: (allow-none): a place to store the result, if any, of @func's execution, or %NULL * @out_job_id: (allow-none): a place to store the ID of the job having been submitted, or %NULL * @func: the function to call from the worker thread * @data: (allow-none): the data to pass to @func, or %NULL * @data_destroy_func: (allow-none): a function to destroy @data, or %NULL * @result_destroy_func: (allow-none): a function to destroy the result, if any, of @func's execution, or %NULL * @error: (allow-none): a place to store errors, or %NULL. * * Request that the worker thread call @func with the @data argument, much like gda_worker_submit_job(), * but waits (starting a #GMainLoop) for a maximum of @timeout_ms miliseconds for @func to be executed. * * If this function is called from within @worker's worker thread, then this function simply calls @func with @data and does not * use @context. * * The following cases are possible if this function is not called from within @worker's worker thread: * <itemizedlist> * <listitem><para>the call to @func took less than @timeout_ms miliseconds: the return value is %TRUE and * @out_result contains the result of the @func's execution, and @out_job_id contains %NULL. Note in this * case that @error may still contain an error code if @func's execution produced an error. Also note that in this case * any setting defined by gda_worker_set_callback() is not applied (as the result is immediately returned)</para></listitem> * <listitem><para>The call to @func takes more then @timeout_ms miliseconds: the return value is %TRUE and * @out_result is %NULL and @out_job_id contains the ID of the job as if it had been submitted using gda_worker_submit_job(). * If @out_job_id is %NULL, and if no setting has been defined using gda_worker_set_callback(), then the job will be discarded * (as if gda_worker_forget_job() had been called). * </para></listitem> * <listitem><para>The call to @func could not be done (some kind of plumbing error for instance): the returned value is %FALSE * and @out_result and @out_job_id are set to %NULL (if they are not %NULL)</para></listitem> * </itemizedlist> * * Notes: * <itemizedlist> * <listitem><para>@result_destroy_func is needed in case @out_result is %NULL (to avoid memory leaks)</para></listitem> * <listitem><para>passing %NULL for @context is similar to passing the result of g_main_context_ref_thread_default()</para></listitem> * </itemizedlist> * * Returns: %TRUE if no error occurred * * Since: 6.0 */ gboolean gda_worker_do_job (GdaWorker *worker, GMainContext *context, gint timeout_ms, gpointer *out_result, guint *out_job_id, GdaWorkerFunc func, gpointer data, GDestroyNotify data_destroy_func, GDestroyNotify result_destroy_func, GError **error) { if (out_result) *out_result = NULL; if (out_job_id) *out_job_id = 0; g_return_val_if_fail (worker, FALSE); g_return_val_if_fail (func, FALSE); if (!worker->callbacks_hash || !worker->jobs_hash) { g_warning ("GdaWorker has been destroyed\n"); return FALSE; } if (gda_worker_thread_is_worker (worker)) { /* we are called from within the worker thread => call the function directly */ gpointer result; result = func (data, error); if (data_destroy_func) data_destroy_func (data); if (out_result) *out_result = result; else if (result && result_destroy_func) result_destroy_func (result); return TRUE; } guint jid, itsid, timer = 0; /* determine which GMainContext to use */ GMainContext *co; gboolean unref_co = FALSE; co = context; if (!co) { co = g_main_context_ref_thread_default (); unref_co = TRUE; } /* prepare main loop */ GMainLoop *loop; loop = g_main_loop_new (co, FALSE); /* prepare ITSignaler to be notified */ ITSignaler *its; its = itsignaler_new (); itsid = itsignaler_add (its, co, (ITSignalerFunc) do_itsignaler_cb, g_main_loop_ref (loop), (GDestroyNotify) g_main_loop_unref); /* push job */ g_rec_mutex_lock (& worker->rmutex); /* required to call _gda_worker_submit_job_with_its() */ jid = _gda_worker_submit_job_with_its (worker, its, func, data, data_destroy_func, result_destroy_func, error); g_rec_mutex_unlock (& worker->rmutex); if (jid == 0) { /* an error occurred */ g_assert (itsignaler_remove (its, co, itsid)); itsignaler_unref (its); g_main_loop_unref (loop); if (unref_co) g_main_context_unref (co); return FALSE; } /* check if result is already here */ WorkerJob *job; job = itsignaler_pop_notification (its, 0); if (!job) { if (timeout_ms > 0) { /* start timer to limit waiting time */ GSource *timer_src; timer_src = g_timeout_source_new (timeout_ms); g_source_set_callback (timer_src, (GSourceFunc) do_timer_cb, loop, NULL); timer = g_source_attach (timer_src, co); g_source_unref (timer_src); } g_main_loop_run (loop); /* either timer has arrived or job has been done */ job = itsignaler_pop_notification (its, 0); } g_main_loop_unref (loop); g_assert (itsignaler_remove (its, co, itsid)); itsignaler_unref (its); if (job) { /* job done before the timer, if any, elapsed */ if (timer > 0) g_assert (g_source_remove (timer)); g_assert (gda_worker_fetch_job_result (worker, jid, out_result, error)); } else { /* timer came first, job is not yet finished */ /* apply settings from gda_worker_set_callback(), if any */ g_rec_mutex_lock (&worker->rmutex); DeclaredCallback *dc; dc = g_hash_table_lookup (worker->callbacks_hash, co); if (dc) { job = g_hash_table_lookup (worker->jobs_hash, &jid); g_assert (job); g_assert (!job->reply_its); job->reply_its = itsignaler_ref (dc->its); } g_rec_mutex_unlock (& worker->rmutex); /* cleanups */ if (out_job_id) *out_job_id = jid; else if (!dc) /* forget all about the job */ gda_worker_forget_job (worker, jid); } if (unref_co) g_main_context_unref (co); return TRUE; }
int main (int argc, char** argv) { g_print ("Test started\n"); ITSignaler *its; its = itsignaler_new (); Data *data; #ifndef PERF g_print ("Try popping initially...\n"); #endif data = itsignaler_pop_notification (its, 0); if (data) { g_warning ("itsignaler_pop_notification() sould have returned NULL"); return EXIT_FAILURE; } #ifndef PERF g_print ("Done\n"); #endif GThread *th; th = g_thread_new ("sub", (GThreadFunc) thread1_start, its); gint counter = 0; guint ticks; #ifdef G_OS_WIN32 struct timeval timeout; fd_set fds; SOCKET sock; timeout.tv_sec = 0; timeout.tv_usec = 100000; sock = itsignaler_windows_get_poll_fd (its); FD_ZERO (&fds); FD_SET (sock, &fds); #else struct pollfd fds[1]; fds[0].fd = itsignaler_unix_get_poll_fd (its); fds[0].events = POLLIN; fds[0].revents = 0; #endif GTimer *timer; timer = g_timer_new (); for (ticks = 0; ticks < MAX_ITERATIONS * 4; ticks++) { data = NULL; int res; #ifndef PERF g_print ("."); fflush (stdout); #endif #ifdef G_OS_WIN32 res = select (1, &fds, 0, 0, &timeout); if (res == SOCKET_ERROR) { g_warning ("Failure on select(): %d", WSAGetLastError ()); /* FIXME! */ return EXIT_FAILURE; } else if (res > 0) { data = (Data*) itsignaler_pop_notification (its, 0); } #else res = poll (fds, 1, 100); if (res == -1) { g_warning ("Failure on poll(): %s", strerror (errno)); return EXIT_FAILURE; } else if (res > 0) { if (fds[0].revents | POLLIN) { data = (Data*) itsignaler_pop_notification (its, 0); } else { g_warning ("Something nasty happened on file desciptor..."); return EXIT_FAILURE; } } #endif if (data) { if (counter != data->counter) { g_warning ("itsignaler_pop_notification() returned wrong value %d instead of %d", data->counter, counter); return EXIT_FAILURE; } else { #ifndef PERF g_print ("Popped %d\n", counter); #endif counter++; } g_free (data); if (counter == MAX_ITERATIONS * 2) break; } } g_timer_stop (timer); g_thread_join (th); itsignaler_unref (its); if (counter == MAX_ITERATIONS * 2) { gdouble duration; duration = g_timer_elapsed (timer, NULL); g_print ("Test Ok, got %u notification in %0.5f s\n", MAX_ITERATIONS * 2, duration); g_timer_destroy (timer); return EXIT_SUCCESS; } else { g_print ("Test Failed: got %d notification(s) out of %u\n", counter, MAX_ITERATIONS * 2); g_timer_destroy (timer); return EXIT_SUCCESS; } }