/* ev-job-scheduler.c * this file is part of atril, a mate document viewer * * Copyright (C) 2008 Carlos Garcia Campos * * Atril is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Atril 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ev-debug.h" #include "ev-job-scheduler.h" typedef struct _EvSchedulerJob { EvJob *job; EvJobPriority priority; GSList *job_link; } EvSchedulerJob; G_LOCK_DEFINE_STATIC(job_list); static GSList *job_list = NULL; static volatile EvJob *running_job = NULL; static gpointer ev_job_thread_proxy (gpointer data); static void ev_scheduler_thread_job_cancelled (EvSchedulerJob *job, GCancellable *cancellable); /* EvJobQueue */ static GQueue queue_urgent = G_QUEUE_INIT; static GQueue queue_high = G_QUEUE_INIT; static GQueue queue_low = G_QUEUE_INIT; static GQueue queue_none = G_QUEUE_INIT; static GCond job_queue_cond; static GMutex job_queue_mutex; static GQueue *job_queue[EV_JOB_N_PRIORITIES] = { &queue_urgent, &queue_high, &queue_low, &queue_none }; static void ev_job_queue_push (EvSchedulerJob *job, EvJobPriority priority) { ev_debug_message (DEBUG_JOBS, "%s priority %d", EV_GET_TYPE_NAME (job->job), priority); g_mutex_lock (&job_queue_mutex); g_queue_push_tail (job_queue[priority], job); g_cond_broadcast (&job_queue_cond); g_mutex_unlock (&job_queue_mutex); } static EvSchedulerJob * ev_job_queue_get_next_unlocked (void) { gint i; EvSchedulerJob *job = NULL; for (i = EV_JOB_PRIORITY_URGENT; i < EV_JOB_N_PRIORITIES; i++) { job = (EvSchedulerJob *) g_queue_pop_head (job_queue[i]); if (job) break; } ev_debug_message (DEBUG_JOBS, "%s", job ? EV_GET_TYPE_NAME (job->job) : "No jobs in queue"); return job; } static gpointer ev_job_scheduler_init (gpointer data) { g_thread_new ("EvJobScheduler", ev_job_thread_proxy, NULL); return NULL; } static void ev_scheduler_job_list_add (EvSchedulerJob *job) { ev_debug_message (DEBUG_JOBS, "%s", EV_GET_TYPE_NAME (job->job)); G_LOCK (job_list); job_list = g_slist_prepend (job_list, job); job->job_link = job_list; G_UNLOCK (job_list); } static void ev_scheduler_job_list_remove (EvSchedulerJob *job) { ev_debug_message (DEBUG_JOBS, "%s", EV_GET_TYPE_NAME (job->job)); G_LOCK (job_list); job_list = g_slist_delete_link (job_list, job->job_link); G_UNLOCK (job_list); } static void ev_scheduler_job_free (EvSchedulerJob *job) { if (!job) return; g_object_unref (job->job); g_free (job); } static void ev_scheduler_job_destroy (EvSchedulerJob *job) { ev_debug_message (DEBUG_JOBS, "%s", EV_GET_TYPE_NAME (job->job)); if (job->job->run_mode == EV_JOB_RUN_MAIN_LOOP) { g_signal_handlers_disconnect_by_func (job->job, G_CALLBACK (ev_scheduler_job_destroy), job); } else { g_signal_handlers_disconnect_by_func (job->job->cancellable, G_CALLBACK (ev_scheduler_thread_job_cancelled), job); } ev_scheduler_job_list_remove (job); ev_scheduler_job_free (job); } static void ev_scheduler_thread_job_cancelled (EvSchedulerJob *job, GCancellable *cancellable) { GList *list; ev_debug_message (DEBUG_JOBS, "%s", EV_GET_TYPE_NAME (job->job)); g_mutex_lock (&job_queue_mutex); /* If the job is not still running, * remove it from the job queue and job list. * If the job is currently running, it will be * destroyed as soon as it finishes. */ list = g_queue_find (job_queue[job->priority], job); if (list) { g_queue_delete_link (job_queue[job->priority], list); g_mutex_unlock (&job_queue_mutex); ev_scheduler_job_destroy (job); } else { g_mutex_unlock (&job_queue_mutex); } } static void ev_job_thread (EvJob *job) { gboolean result; ev_debug_message (DEBUG_JOBS, "%s", EV_GET_TYPE_NAME (job)); do { if (g_cancellable_is_cancelled (job->cancellable)) result = FALSE; else { g_atomic_pointer_set (&running_job, job); result = ev_job_run (job); } } while (result); g_atomic_pointer_set (&running_job, NULL); } static gboolean ev_job_idle (EvJob *job) { ev_debug_message (DEBUG_JOBS, "%s", EV_GET_TYPE_NAME (job)); if (g_cancellable_is_cancelled (job->cancellable)) return FALSE; return ev_job_run (job); } static gpointer ev_job_thread_proxy (gpointer data) { while (TRUE) { EvSchedulerJob *job; g_mutex_lock (&job_queue_mutex); job = ev_job_queue_get_next_unlocked (); if (!job) { g_cond_wait (&job_queue_cond, &job_queue_mutex); g_mutex_unlock (&job_queue_mutex); continue; } g_mutex_unlock (&job_queue_mutex); ev_job_thread (job->job); ev_scheduler_job_destroy (job); } return NULL; } void ev_job_scheduler_push_job (EvJob *job, EvJobPriority priority) { static GOnce once_init = G_ONCE_INIT; EvSchedulerJob *s_job; g_once (&once_init, ev_job_scheduler_init, NULL); ev_debug_message (DEBUG_JOBS, "%s pirority %d", EV_GET_TYPE_NAME (job), priority); s_job = g_new0 (EvSchedulerJob, 1); s_job->job = g_object_ref (job); s_job->priority = priority; ev_scheduler_job_list_add (s_job); switch (ev_job_get_run_mode (job)) { case EV_JOB_RUN_THREAD: g_signal_connect_swapped (job->cancellable, "cancelled", G_CALLBACK (ev_scheduler_thread_job_cancelled), s_job); ev_job_queue_push (s_job, priority); break; case EV_JOB_RUN_MAIN_LOOP: g_signal_connect_swapped (job, "finished", G_CALLBACK (ev_scheduler_job_destroy), s_job); g_signal_connect_swapped (job, "cancelled", G_CALLBACK (ev_scheduler_job_destroy), s_job); g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc)ev_job_idle, g_object_ref (job), (GDestroyNotify)g_object_unref); break; default: g_assert_not_reached (); } } void ev_job_scheduler_update_job (EvJob *job, EvJobPriority priority) { GSList *l; EvSchedulerJob *s_job = NULL; gboolean need_resort = FALSE; /* Main loop jobs are scheduled inmediately */ if (ev_job_get_run_mode (job) == EV_JOB_RUN_MAIN_LOOP) return; ev_debug_message (DEBUG_JOBS, "%s pirority %d", EV_GET_TYPE_NAME (job), priority); G_LOCK (job_list); for (l = job_list; l; l = l->next) { s_job = (EvSchedulerJob *)l->data; if (s_job->job == job) { need_resort = (s_job->priority != priority); break; } } G_UNLOCK (job_list); if (need_resort) { GList *list; g_mutex_lock (&job_queue_mutex); list = g_queue_find (job_queue[s_job->priority], s_job); if (list) { ev_debug_message (DEBUG_JOBS, "Moving job %s from pirority %d to %d", EV_GET_TYPE_NAME (job), s_job->priority, priority); g_queue_delete_link (job_queue[s_job->priority], list); g_queue_push_tail (job_queue[priority], s_job); g_cond_broadcast (&job_queue_cond); } g_mutex_unlock (&job_queue_mutex); } } /** * ev_job_scheduler_get_running_thread_job: * * Returns: (transfer none): an #EvJob */ EvJob * ev_job_scheduler_get_running_thread_job (void) { return g_atomic_pointer_get (&running_job); }