* interface for collectd plugins written in perl.
*/
-/* do not automatically get the thread specific perl interpreter */
+/* do not automatically get the thread specific Perl interpreter */
#define PERL_NO_GET_CONTEXT
#define DONT_POISON_SPRINTF_YET 1
static XS (Collectd_plugin_register_ds);
static XS (Collectd_plugin_unregister_ds);
static XS (Collectd_plugin_dispatch_values);
+static XS (Collectd_plugin_get_interval);
static XS (Collectd__plugin_write);
static XS (Collectd__plugin_flush);
static XS (Collectd_plugin_dispatch_notification);
typedef struct c_ithread_s {
/* the thread's Perl interpreter */
PerlInterpreter *interp;
+ _Bool running; /* thread is inside Perl interpreter */
+ _Bool shutdown;
+ pthread_t pthread;
/* double linked list of threads */
struct c_ithread_s *prev;
#endif /* COLLECT_DEBUG */
pthread_mutex_t mutex;
+ pthread_mutexattr_t mutexattr;
} c_ithread_list_t;
/* name / user_data for Perl matches / targets */
{ "Collectd::plugin_register_data_set", Collectd_plugin_register_ds },
{ "Collectd::plugin_unregister_data_set", Collectd_plugin_unregister_ds },
{ "Collectd::plugin_dispatch_values", Collectd_plugin_dispatch_values },
+ { "Collectd::plugin_get_interval", Collectd_plugin_get_interval },
{ "Collectd::_plugin_write", Collectd__plugin_write },
{ "Collectd::_plugin_flush", Collectd__plugin_flush },
{ "Collectd::plugin_dispatch_notification",
{ "", NULL }
};
-struct {
- char name[64];
- int *var;
-} g_integers[] =
-{
- { "Collectd::interval_g", &interval_g },
- { "", NULL }
-};
-
/*
* Helper functions for data type conversion.
*/
}
if (NULL != (tmp = hv_fetch (hash, "time", 4, 0)))
- vl->time = (time_t)SvIV (*tmp);
+ {
+ double t = SvNV (*tmp);
+ vl->time = DOUBLE_TO_CDTIME_T (t);
+ }
if (NULL != (tmp = hv_fetch (hash, "interval", 8, 0)))
- vl->interval = SvIV (*tmp);
+ {
+ double t = SvNV (*tmp);
+ vl->interval = DOUBLE_TO_CDTIME_T (t);
+ }
if (NULL != (tmp = hv_fetch (hash, "host", 4, 0)))
sstrncpy (vl->host, SvPV_nolen (*tmp), sizeof (vl->host));
if (NULL == (tmp = hv_fetch (hash, "value", 5, 0))) {
log_warn ("av2notification_meta: Skipping invalid "
"meta information.");
- free ((*m)->name);
free (*m);
continue;
}
n->severity = NOTIF_FAILURE;
if (NULL != (tmp = hv_fetch (hash, "time", 4, 0)))
- n->time = (time_t)SvIV (*tmp);
+ {
+ double t = SvNV (*tmp);
+ n->time = DOUBLE_TO_CDTIME_T (t);
+ }
else
- n->time = time (NULL);
+ n->time = cdtime ();
if (NULL != (tmp = hv_fetch (hash, "message", 7, 0)))
sstrncpy (n->message, SvPV_nolen (*tmp), sizeof (n->message));
return -1;
if (0 != vl->time)
- if (NULL == hv_store (hash, "time", 4, newSViv (vl->time), 0))
+ {
+ double t = CDTIME_T_TO_DOUBLE (vl->time);
+ if (NULL == hv_store (hash, "time", 4, newSVnv (t), 0))
return -1;
+ }
- if (NULL == hv_store (hash, "interval", 8, newSViv (vl->interval), 0))
- return -1;
+ {
+ double t = CDTIME_T_TO_DOUBLE (vl->interval);
+ if (NULL == hv_store (hash, "interval", 8, newSVnv (t), 0))
+ return -1;
+ }
if ('\0' != vl->host[0])
if (NULL == hv_store (hash, "host", 4, newSVpv (vl->host, 0), 0))
return -1;
if (0 != n->time)
- if (NULL == hv_store (hash, "time", 4, newSViv (n->time), 0))
+ {
+ double t = CDTIME_T_TO_DOUBLE (n->time);
+ if (NULL == hv_store (hash, "time", 4, newSVnv (t), 0))
return -1;
+ }
if ('\0' != *n->message)
if (NULL == hv_store (hash, "message", 7, newSVpv (n->message, 0), 0))
} /* static int pplugin_dispatch_notification (HV *) */
/*
+ * Call perl sub with thread locking flags handled.
+ */
+static int call_pv_locked (pTHX_ const char* sub_name)
+{
+ _Bool old_running;
+ int ret;
+
+ c_ithread_t *t = (c_ithread_t *)pthread_getspecific(perl_thr_key);
+ if (t == NULL) /* thread destroyed */
+ return 0;
+
+ old_running = t->running;
+ t->running = 1;
+
+ if (t->shutdown) {
+ t->running = old_running;
+ return 0;
+ }
+
+ ret = call_pv (sub_name, G_SCALAR);
+
+ t->running = old_running;
+ return ret;
+} /* static int call_pv_locked (pTHX, *sub_name) */
+
+/*
* Call all working functions of the given type.
*/
static int pplugin_call_all (pTHX_ int type, ...)
XPUSHs (sv_2mortal (newRV_noinc ((SV *)notif)));
}
else if (PLUGIN_FLUSH == type) {
+ cdtime_t timeout;
+
/*
* $_[0] = $timeout;
* $_[1] = $identifier;
*/
- XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
+ timeout = va_arg (ap, cdtime_t);
+
+ XPUSHs (sv_2mortal (newSVnv (CDTIME_T_TO_DOUBLE (timeout))));
XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
}
PUTBACK;
- retvals = call_pv ("Collectd::plugin_call_all", G_SCALAR);
+ retvals = call_pv_locked (aTHX_ "Collectd::plugin_call_all");
SPAGAIN;
if (0 < retvals) {
} /* static int pplugin_call_all (int, ...) */
/*
- * collectd's perl interpreter based thread implementation.
+ * collectd's Perl interpreter based thread implementation.
*
* This has been inspired by Perl's ithreads introduced in version 5.6.0.
*/
/* the ithread no longer exists */
if (NULL == t)
+ {
+ pthread_mutex_unlock (&perl_threads->mutex);
return;
+ }
c_ithread_destroy (ithread);
t->prev = perl_threads->tail;
}
+ t->pthread = pthread_self();
+ t->running = 0;
+ t->shutdown = 0;
perl_threads->tail = t;
pthread_setspecific (perl_thr_key, (const void *)t);
PUTBACK;
- retvals = call_pv ("Collectd::fc_call", G_SCALAR);
+ retvals = call_pv_locked (aTHX_ "Collectd::fc_call");
if ((FC_CB_EXEC == cb_type) && (meta != NULL)) {
assert (pmeta != NULL);
values = ST (/* stack index = */ 0);
+ if (NULL == values)
+ XSRETURN_EMPTY;
+
/* Make sure the argument is a hash reference. */
if (! (SvROK (values) && (SVt_PVHV == SvTYPE (SvRV (values))))) {
log_err ("Collectd::plugin_dispatch_values: Invalid values.");
XSRETURN_EMPTY;
}
- if (NULL == values)
- XSRETURN_EMPTY;
-
ret = pplugin_dispatch_values (aTHX_ (HV *)SvRV (values));
if (0 == ret)
XSRETURN_EMPTY;
} /* static XS (Collectd_plugin_dispatch_values) */
+/*
+ * Collectd::plugin_get_interval ().
+ */
+static XS (Collectd_plugin_get_interval)
+{
+ dXSARGS;
+
+ /* make sure we don't get any unused variable warnings for 'items';
+ * don't abort, though */
+ if (items)
+ log_err ("Usage: Collectd::plugin_get_interval()");
+
+ XSRETURN_NV ((NV) CDTIME_T_TO_DOUBLE (plugin_get_interval ()));
+} /* static XS (Collectd_plugin_get_interval) */
+
/* Collectd::plugin_write (plugin, ds, vl).
*
* plugin:
static int perl_init (void)
{
+ int status;
dTHX;
if (NULL == perl_threads)
log_debug ("perl_init: c_ithread: interp = %p (active threads: %i)",
aTHX, perl_threads->number_of_threads);
- return pplugin_call_all (aTHX_ PLUGIN_INIT);
+
+ /* Lock the base thread to avoid race conditions with c_ithread_create().
+ * See https://github.com/collectd/collectd/issues/9 and
+ * https://github.com/collectd/collectd/issues/1706 for details.
+ */
+ assert (aTHX == perl_threads->head->interp);
+ pthread_mutex_lock (&perl_threads->mutex);
+
+ status = pplugin_call_all (aTHX_ PLUGIN_INIT);
+
+ pthread_mutex_unlock (&perl_threads->mutex);
+
+ return status;
} /* static int perl_init (void) */
static int perl_read (void)
aTHX = t->interp;
}
+ /* Assert that we're not running as the base thread. Otherwise, we might
+ * run into concurrency issues with c_ithread_create(). See
+ * https://github.com/collectd/collectd/issues/9 for details. */
+ assert (aTHX != perl_threads->head->interp);
+
log_debug ("perl_read: c_ithread: interp = %p (active threads: %i)",
aTHX, perl_threads->number_of_threads);
return pplugin_call_all (aTHX_ PLUGIN_READ);
static int perl_write (const data_set_t *ds, const value_list_t *vl,
user_data_t __attribute__((unused)) *user_data)
{
+ int status;
dTHX;
if (NULL == perl_threads)
aTHX = t->interp;
}
+ /* Lock the base thread if this is not called from one of the read threads
+ * to avoid race conditions with c_ithread_create(). See
+ * https://github.com/collectd/collectd/issues/9 for details. */
+ if (aTHX == perl_threads->head->interp)
+ pthread_mutex_lock (&perl_threads->mutex);
+
log_debug ("perl_write: c_ithread: interp = %p (active threads: %i)",
aTHX, perl_threads->number_of_threads);
- return pplugin_call_all (aTHX_ PLUGIN_WRITE, ds, vl);
+ status = pplugin_call_all (aTHX_ PLUGIN_WRITE, ds, vl);
+
+ if (aTHX == perl_threads->head->interp)
+ pthread_mutex_unlock (&perl_threads->mutex);
+
+ return status;
} /* static int perl_write (const data_set_t *, const value_list_t *) */
static void perl_log (int level, const char *msg,
aTHX = t->interp;
}
+ /* Lock the base thread if this is not called from one of the read threads
+ * to avoid race conditions with c_ithread_create(). See
+ * https://github.com/collectd/collectd/issues/9 for details.
+ */
+
+ if (aTHX == perl_threads->head->interp)
+ pthread_mutex_lock (&perl_threads->mutex);
+
pplugin_call_all (aTHX_ PLUGIN_LOG, level, msg);
+
+ if (aTHX == perl_threads->head->interp)
+ pthread_mutex_unlock (&perl_threads->mutex);
+
return;
} /* static void perl_log (int, const char *) */
return pplugin_call_all (aTHX_ PLUGIN_NOTIF, notif);
} /* static int perl_notify (const notification_t *) */
-static int perl_flush (int timeout, const char *identifier,
+static int perl_flush (cdtime_t timeout, const char *identifier,
user_data_t __attribute__((unused)) *user_data)
{
dTHX;
return 0;
if (NULL == aTHX) {
- c_ithread_t *t = NULL;
+ t = NULL;
pthread_mutex_lock (&perl_threads->mutex);
t = c_ithread_create (perl_threads->head->interp);
t = perl_threads->tail;
while (NULL != t) {
+ struct timespec ts_wait;
c_ithread_t *thr = t;
/* the pointer has to be advanced before destroying
* the thread as this will free the memory */
t = t->prev;
+ thr->shutdown = 1;
+ if (thr->running) {
+ /* Give some time to thread to exit from Perl interpreter */
+ WARNING ("perl shutdown: Thread is running inside Perl. Waiting.");
+ ts_wait.tv_sec = 0;
+ ts_wait.tv_nsec = 500000;
+ nanosleep (&ts_wait, NULL);
+ }
+ if (thr->running) {
+ pthread_kill (thr->pthread, SIGTERM);
+ ERROR ("perl shutdown: Thread hangs inside Perl. Thread killed.");
+ }
c_ithread_destroy (thr);
}
pthread_mutex_unlock (&perl_threads->mutex);
pthread_mutex_destroy (&perl_threads->mutex);
+ pthread_mutexattr_destroy (&perl_threads->mutexattr);
sfree (perl_threads);
return 0;
} /* static int g_pv_set (pTHX_ SV *, MAGIC *) */
-static int g_iv_get (pTHX_ SV *var, MAGIC *mg)
+static int g_interval_get (pTHX_ SV *var, MAGIC *mg)
{
- int *iv = (int *)mg->mg_ptr;
- sv_setiv (var, *iv);
+ log_warn ("Accessing $interval_g is deprecated (and might not "
+ "give the desired results) - plugin_get_interval() should "
+ "be used instead.");
+ sv_setnv (var, CDTIME_T_TO_DOUBLE (interval_g));
return 0;
-} /* static int g_iv_get (pTHX_ SV *, MAGIC *) */
+} /* static int g_interval_get (pTHX_ SV *, MAGIC *) */
-static int g_iv_set (pTHX_ SV *var, MAGIC *mg)
+static int g_interval_set (pTHX_ SV *var, MAGIC *mg)
{
- int *iv = (int *)mg->mg_ptr;
- *iv = (int)SvIV (var);
+ double nv = (double)SvNV (var);
+ log_warn ("Accessing $interval_g is deprecated (and might not "
+ "give the desired results) - plugin_get_interval() should "
+ "be used instead.");
+ interval_g = DOUBLE_TO_CDTIME_T (nv);
return 0;
-} /* static int g_iv_set (pTHX_ SV *, MAGIC *) */
+} /* static int g_interval_set (pTHX_ SV *, MAGIC *) */
static MGVTBL g_pv_vtbl = {
g_pv_get, g_pv_set, NULL, NULL, NULL, NULL, NULL
, NULL
#endif
};
-static MGVTBL g_iv_vtbl = {
- g_iv_get, g_iv_set, NULL, NULL, NULL, NULL, NULL
+static MGVTBL g_interval_vtbl = {
+ g_interval_get, g_interval_set, NULL, NULL, NULL, NULL, NULL
#if HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL
, NULL
#endif
g_strings[i].var, 0);
}
- /* global integers */
- for (i = 0; '\0' != g_integers[i].name[0]; ++i) {
- tmp = get_sv (g_integers[i].name, 1);
- sv_magicext (tmp, NULL, PERL_MAGIC_ext, &g_iv_vtbl,
- (char *)g_integers[i].var, 0);
- }
+ tmp = get_sv ("Collectd::interval_g", /* create = */ 1);
+ sv_magicext (tmp, NULL, /* how = */ PERL_MAGIC_ext,
+ /* vtbl = */ &g_interval_vtbl,
+ /* name = */ NULL, /* namelen = */ 0);
+
return;
} /* static void xs_init (pTHX) */
perl_threads = (c_ithread_list_t *)smalloc (sizeof (c_ithread_list_t));
memset (perl_threads, 0, sizeof (c_ithread_list_t));
- pthread_mutex_init (&perl_threads->mutex, NULL);
+ pthread_mutexattr_init(&perl_threads->mutexattr);
+ pthread_mutexattr_settype(&perl_threads->mutexattr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init (&perl_threads->mutex, &perl_threads->mutexattr);
/* locking the mutex should not be necessary at this point
* but let's just do it for the sake of completeness */
pthread_mutex_lock (&perl_threads->mutex);
aTHX = perl_threads->head->interp;
- log_debug ("perl_config: loading perl plugin \"%s\"", value);
+ log_debug ("perl_config: Loading Perl plugin \"%s\"", value);
load_module (PERL_LOADMOD_NOIMPORT,
newSVpv (module_name, strlen (module_name)), Nullsv);
return 0;
int current_status = 0;
if (NULL != perl_threads)
- aTHX = PERL_GET_CONTEXT;
+ {
+ if ((aTHX = PERL_GET_CONTEXT) == NULL)
+ return -1;
+ }
if (0 == strcasecmp (c->key, "LoadPlugin"))
current_status = perl_config_loadplugin (aTHX_ c);