12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <arpa/inet.h>
17 # define NI_MAXHOST 1025
20 # define NI_MAXSERV 32
23 #include <sys/resource.h> // needs _GNU_SOURCE for RUSAGE_THREAD
25 #include <event2/bufferevent.h>
26 #include <event2/buffer.h>
27 #include <event2/event.h>
28 #include <event2/util.h>
30 #include <ossp/uuid.h>
34 #include "connections.h"
39 * A reference-counted data buffer.
40 * Used internally here for sending out one message to multiple connections, without
41 * creating a copy for each connection.
42 * Frees data pointer when the last reference is removed.
43 * A connection thread increases the reference count as it sends the message to each
44 * destination connection, while the libevent thread decrements as it completes each
48 pthread_mutex_t mutex
;
49 size_t reference_count
;
55 * Allocates, initializes, and returns a new struct rc_data_ entity.
57 static struct rc_data_
*
58 rc_data_new_(char *data
, size_t len
)
64 NOTIFY_DEBUG("data:%p len:%zu", data
, len
);
68 rc
= malloc(sizeof *rc
);
70 NOTIFY_ERROR("malloc(%zu): %s", sizeof *rc
, strerror(errno
));
74 if ( (r
= pthread_mutex_init(&rc
->mutex
, NULL
)) ) {
75 NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r
));
80 rc
->reference_count
= 1;
88 * Increments the reference count of a struct rc_data_ entity.
91 rc_data_ref_inc_(struct rc_data_
*rc
)
95 if ( (r
= pthread_mutex_lock(&rc
->mutex
)) ) {
96 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
100 rc
->reference_count
+= 1;
102 if ( (r
= pthread_mutex_unlock(&rc
->mutex
)) ) {
103 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
108 * Decrements the reference count of a struct rc_data_ entity
109 * and frees everything if there are no more references.
112 rc_data_free_(struct rc_data_
*rc
)
116 if ( (r
= pthread_mutex_lock(&rc
->mutex
)) ) {
117 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
120 rc
->reference_count
-= 1;
121 if (rc
->reference_count
> 0) {
122 if ( (r
= pthread_mutex_unlock(&rc
->mutex
)) ) {
123 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
130 if ( (r
= pthread_mutex_unlock(&rc
->mutex
)) ) {
131 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
133 if ( (r
= pthread_mutex_destroy(&rc
->mutex
)) ) {
134 NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r
));
137 memset(rc
, 0, sizeof *rc
);
143 * Wrapper for rc_data_free_() of the proper callback type to be used by
144 * evbuffer_add_reference() in connection_multi_send().
147 rc_cleanup_cb_(const void *data UNUSED
, size_t len UNUSED
, void *arg
)
149 struct rc_data_
*r
= arg
;
155 * Initializes a struct connections entity.
158 connections_init(struct connections
*cs
)
163 TAILQ_INIT(&cs
->head
);
165 if ( (r
= pthread_mutex_init(&cs
->mutex
, NULL
)) ) {
166 NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r
));
174 * Cleanup a struct connections.
177 connections_fini(struct connections
*cs
)
179 struct connection
*c1
, *c2
;
183 NOTIFY_DEBUG("cs:%p", cs
);
186 /* free any connection entities */
187 c1
= TAILQ_FIRST(&cs
->head
);
189 c2
= TAILQ_NEXT(c1
, tailq_entry
);
193 TAILQ_INIT(&cs
->head
);
196 if ( (r
= pthread_mutex_destroy(&cs
->mutex
)) ) {
197 NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r
));
203 * Inserts a connection at the end of connections list.
206 connections_append(struct connection
*c
)
208 struct connections
*cs
= &c
->server
->connections
;
212 NOTIFY_DEBUG("c:%p", c
);
215 if ( (r
= pthread_mutex_lock(&cs
->mutex
)) ) {
216 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
220 TAILQ_INSERT_TAIL(&cs
->head
, c
, tailq_entry
);
223 if ( (r
= pthread_mutex_unlock(&cs
->mutex
)) ) {
224 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
230 * Removes a connection from its struct connections list.
233 connections_remove(struct connection
*c
)
235 struct connections
*cs
= &c
->server
->connections
;
239 NOTIFY_DEBUG("c:%p", c
);
243 NOTIFY_DEBUG("c:%p connections:%p", c
, cs
);
247 if ( (r
= pthread_mutex_lock(&cs
->mutex
)) ) {
248 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
252 TAILQ_REMOVE(&cs
->head
, c
, tailq_entry
);
255 if ( (r
= pthread_mutex_unlock(&cs
->mutex
)) ) {
256 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
262 * Allocates and returns a null-terminated array of pointers to all connection entities in
263 * connections list, minus the 'c_exclude' connection, if set.
264 * Used as a simple implementation of broadcasting a message.
265 * This increments the reference count of each connection added to the array
266 * so they need to individually be freed before freeing the array.
269 connections_all_as_array(struct connections
*cs
, struct connection
*c_exclude
)
271 struct connection
**all
;
272 struct connection
*c
;
276 if ( (r
= pthread_mutex_lock(&cs
->mutex
)) ) {
277 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
281 all
= calloc(cs
->count
+ 1, sizeof *all
);
283 NOTIFY_ERROR("calloc(%zu, %zu): %s", cs
->count
+ 1, sizeof *all
, strerror(errno
));
287 TAILQ_FOREACH(c
, &cs
->head
, tailq_entry
) {
289 && c
->state
< CONNECTION_STATE_WANT_CLOSE
290 && c
->state
> CONNECTION_STATE_INIT
) {
291 connection_inc_ref(c
);
296 if ( (r
= pthread_mutex_unlock(&cs
->mutex
)) ) {
297 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
309 if ( (r
= pthread_mutex_unlock(&cs
->mutex
)) ) {
310 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
317 * Wrap ev timeout event to re-resolve hostname.
320 connection_resolve_retry_event_(evutil_socket_t fd UNUSED
, short what UNUSED
, void *arg
)
322 struct connection
*c
= arg
;
324 NOTIFY_DEBUG("retrying hostname resolution");
326 c
->dns_retry_ev
= NULL
;
328 connection_resolve_hostname(c
);
332 * A callback used by connection_init to populate the reverse-lookup of a connection's
336 connection_reverse_dns_cb_(int result
, char type
, int count
, int ttl UNUSED
, void *addresses
, void *ctx
)
338 struct connection
*c
= ctx
;
339 char *old_client_address
= c
->client_address
;
341 if (result
!= DNS_ERR_NONE
) {
342 NOTIFY_ERROR("Error resolving: %s",
343 evdns_err_to_string(result
) );
344 c
->evdns_request
= NULL
;
346 // TODO: configurable
347 // if (server_get_config_integer(c->server, "dns_retries_max")) {
348 // if (server_get_config_integer(c->server, "dns_retry_seconds")) {
350 if (c
->dns_retries
< 4) {
351 struct timeval _seconds
= {30, 0};
352 c
->dns_retry_ev
= event_new(c
->server
->base
, -1, EV_TIMEOUT
, connection_resolve_retry_event_
, c
);
353 event_add(c
->dns_retry_ev
, &_seconds
);
364 NOTIFY_DEBUG("multiple records returned, using first");
366 c
->client_address
= strdup(((char **)addresses
)[0]);
367 NOTIFY_DEBUG("resolved [%s] as %s for '%s' (%p)", old_client_address
, c
->client_address
, c
->name
, c
);
371 NOTIFY_DEBUG("reverse lookup of [%s] returned dns type %d", c
->client_address
, type
);
374 c
->evdns_request
= NULL
;
376 if (old_client_address
) {
377 free(old_client_address
);
383 * Perform reverse lookup of connection address.
386 connection_resolve_hostname(struct connection
*c
)
388 if (c
->evdns_request
) {
389 NOTIFY_DEBUG("resolution already in progress");
393 /* does libevent's evdns have a getnameinfo style call? */
395 switch (c
->sa
->sa_family
) {
397 c
->evdns_request
= evdns_base_resolve_reverse(c
->server
->evdns_base
, &((struct sockaddr_in
*)c
->sa
)->sin_addr
, 0, connection_reverse_dns_cb_
, c
);
401 c
->evdns_request
= evdns_base_resolve_reverse_ipv6(c
->server
->evdns_base
, &((struct sockaddr_in6
*)c
->sa
)->sin6_addr
, 0, connection_reverse_dns_cb_
, c
);
405 NOTIFY_DEBUG("unhandled address family %u", c
->sa
->sa_family
);
407 if (!c
->evdns_request
) {
408 NOTIFY_DEBUG("could not submit PTR lookup request");
414 * Populates a connection with initial information and sets reference count.
417 connection_init(struct server
*s
, struct connection
*c
, struct bufferevent
*bev
, struct sockaddr
*sock
, int socklen
, connection_flags_t flags
)
419 char hbuf
[NI_MAXHOST
], sbuf
[NI_MAXSERV
];
423 if ( (r
= pthread_mutex_init(&c
->rc_mutex
, NULL
)) ) {
424 NOTIFY_ERROR("%s:%s", "pthread_mutex_init", strerror(r
));
428 if ( (r
= pthread_mutex_init(&c
->commands_mutex
, NULL
)) ) {
429 NOTIFY_ERROR("%s:%s", "pthead_mutex_init", strerror(r
));
430 goto err_rc_mutex_destroy
;
433 rc
= uuid_create(&c
->uuid
);
434 if (rc
!= UUID_RC_OK
) {
435 NOTIFY_ERROR("%s:%s", "uuid_create", uuid_error(rc
));
436 goto err_commands_mutex_destroy
;
439 rc
= uuid_make(c
->uuid
, UUID_MAKE_V1
|UUID_MAKE_MC
);
440 if (rc
!= UUID_RC_OK
) {
441 NOTIFY_ERROR("%s:%s", "uuid_make", uuid_error(rc
));
442 goto err_uuid_destroy
;
445 c
->state
= CONNECTION_STATE_INIT
;
450 /* render an IP from a sockaddr */
451 r
= getnameinfo(sock
, socklen
, hbuf
, sizeof hbuf
, sbuf
, sizeof sbuf
, NI_NUMERICHOST
| NI_NUMERICSERV
);
453 NOTIFY_ERROR("%s:%s", "getnameinfo", gai_strerror(r
));
454 strncpy(hbuf
, "(unknown)", sizeof hbuf
);
455 strncpy(sbuf
, "(?)", sizeof sbuf
);
457 c
->client_address
= strdup(hbuf
);
458 if (!c
->client_address
) {
459 NOTIFY_ERROR("%s:%s", "strdup", strerror(errno
));
460 goto err_uuid_destroy
;
463 /* Past this point, errors are non-fatal. */
464 c
->reference_count
= 1;
466 /* Now try to resolve a name from client ip, in background. */
470 c
->dns_retry_ev
= NULL
;
471 connection_resolve_hostname(c
);
473 /* FIXME: temporary name of connection for POC */
474 static int current_connection_number
;
475 r
= snprintf((char *)c
->name
, sizeof(c
->name
), "conn %d", current_connection_number
++);
476 if ((size_t)r
>= sizeof c
->name
) {
477 NOTIFY_ERROR("buffer truncated [%s:%d]", __FILE__
, __LINE__
);
481 /* Darwin systems <10.12 lack clock_gettime (!) */
482 if (gettimeofday(&c
->connect_time
, NULL
)) {
483 NOTIFY_ERROR("%s:%s", "gettimeofday", strerror(errno
));
485 NOTIFY_DEBUG("c:%p [%s] from %s at %.24s", c
, c
->name
, c
->client_address
, ctime(&c
->connect_time
.tv_sec
));
487 if (clock_gettime(CLOCK_REALTIME
, &c
->connect_timespec
)) {
488 NOTIFY_ERROR("%s:%s", "clock_gettime", strerror(errno
));
490 NOTIFY_DEBUG("c:%p [%s] from %s at %.24s", c
, c
->name
, c
->client_address
, ctime(&c
->connect_timespec
.tv_sec
));
496 rc
= uuid_destroy(c
->uuid
);
497 if (rc
!= UUID_RC_OK
) {
498 NOTIFY_ERROR("%s:%s", "uuid_destroy", uuid_error(rc
));
500 err_commands_mutex_destroy
:
501 if ( (r
= pthread_mutex_destroy(&c
->commands_mutex
)) ) {
502 NOTIFY_ERROR("%s:%s", "pthread_destroy", strerror(r
));
504 err_rc_mutex_destroy
:
505 if ( (r
= pthread_mutex_destroy(&c
->rc_mutex
)) ) {
506 NOTIFY_ERROR("%s:%s", "pthread_destroy", strerror(r
));
514 * Process a connection's commands as long as there are commands to process.
515 * FIXME: track how much time has been spend consuming commands, re-queue
516 * processor job when over some threshold considering pending workqueue
519 connection_process_queue_(void *data
, void *context
, size_t id
)
521 struct connection
*c
= data
;
522 struct server_worker_context
*ctx
= context
;
523 struct command
*command
;
524 struct rusage ru_start
, ru_end
;
525 struct timespec ts_start
, ts_current
;
528 if (clock_gettime(CLOCK_MONOTONIC
, &ts_start
) < 0) {
529 NOTIFY_DEBUG("%s:%s", "clock_gettime", strerror(errno
));
532 if ( (r
= pthread_mutex_lock(&c
->commands_mutex
)) ) {
533 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
537 command
= STAILQ_FIRST(&c
->commands_head
);
539 STAILQ_REMOVE_HEAD(&c
->commands_head
, stailq_entry
);
542 if ( (r
= pthread_mutex_unlock(&c
->commands_mutex
)) ) {
543 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
547 if (command
== NULL
) {
551 if ( (r
= getrusage(RUSAGE_THREAD
, &ru_start
)) ) {
552 NOTIFY_ERROR("%s:%s", "getrusage", strerror(r
));
555 command_parse(c
, context
, id
, command
);
557 if ( (r
= getrusage(RUSAGE_THREAD
, &ru_end
)) ) {
558 NOTIFY_ERROR("%s:%s", "getrusage", strerror(r
));
561 /* track how much time was spent procesing */
562 connection_accounting_increment(c
, &ru_start
, &ru_end
);
564 command_free(command
);
566 if (clock_gettime(CLOCK_MONOTONIC
, &ts_current
) < 0) {
567 NOTIFY_ERROR("%s:%s", "clock_gettime", strerror(errno
));
573 // Done processing for a connection, force GC cycle to release additional c references.
574 lua_gc(ctx
->L
, LUA_GCCOLLECT
);
579 * Adds a command to a connection's list of things to process.
582 connection_command_enqueue(struct connection
*c
, struct command
*command
)
587 if ( (r
= pthread_mutex_lock(&c
->commands_mutex
)) ) {
588 command_free(command
);
589 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
593 was_empty
= STAILQ_EMPTY(&c
->commands_head
) ? true : false;
594 if (was_empty
== true) {
595 STAILQ_INSERT_HEAD(&c
->commands_head
, command
, stailq_entry
);
597 STAILQ_INSERT_TAIL(&c
->commands_head
, command
, stailq_entry
);
601 /* Darwin systems <10.12 lack clock_gettime (!) */
602 if (gettimeofday(&c
->last_received_time
, NULL
)) {
603 NOTIFY_ERROR("%s:%s", "gettimeofday", strerror(errno
));
606 if (clock_gettime(CLOCK_REALTIME
, &c
->last_received_timespec
)) {
607 NOTIFY_ERROR("%s:%s", "clock_gettime", strerror(errno
));
611 if ( (r
= pthread_mutex_unlock(&c
->commands_mutex
)) ) {
612 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
616 /* if there were already queued commands, a processor should already be scheduled */
617 if (was_empty
== true) {
618 connection_inc_ref(c
);
619 if (workqueue_add(&c
->server
->workqueue
, connection_process_queue_
, c
)) {
620 NOTIFY_ERROR("%s:%s", "workqueue_add", "failed");
630 * Add the utime and stime values to a connection's totals.
633 connection_accounting_increment(struct connection
*c
, struct rusage
*ru_start
, struct rusage
*ru_end
)
635 struct timeval utime_diff
, stime_diff
;
637 timeval_diff(&utime_diff
, &ru_end
->ru_utime
, &ru_start
->ru_utime
);
638 timeval_diff(&stime_diff
, &ru_end
->ru_stime
, &ru_start
->ru_stime
);
640 timeval_increment(&c
->utime
, &utime_diff
);
641 timeval_increment(&c
->stime
, &stime_diff
);
645 * Locks the output buffer, for sending multiple
646 * contiguous messages without possible interleaving.
649 connection_lock_output(struct connection
*c
)
651 struct evbuffer
*output
= bufferevent_get_output(c
->bev
);
653 evbuffer_lock(output
);
657 * Unlocks the output buffer.
660 connection_unlock_output(struct connection
*c
)
662 struct evbuffer
*output
= bufferevent_get_output(c
->bev
);
664 evbuffer_unlock(output
);
668 * Sends a text string to a connection.
671 connection_printf(struct connection
*c
, const char *fmt
, ...)
673 struct evbuffer
*output
= bufferevent_get_output(c
->bev
);
678 r
= evbuffer_add_vprintf(output
, fmt
, ap
);
686 * Sends a text string to a connection.
689 connection_vprintf(struct connection
*c
, const char *fmt
, va_list ap
)
691 struct evbuffer
*output
= bufferevent_get_output(c
->bev
);
694 r
= evbuffer_add_vprintf(output
, fmt
, ap
);
701 * Send one data buffer to multiple connections.
702 * Data buffer must be malloced, and will be freed when last connection
703 * has finished with it.
704 * Returns number of items sent, or negative on error.
707 connection_multi_send(struct connection
**clist
, char *data
, size_t len
)
709 struct connection
**clist_iter
;
714 NOTIFY_DEBUG("null clist [%s:%d]", __FILE__
, __LINE__
);
718 /* Track our data so it can be freed once all connections are done with it. */
719 rc
= rc_data_new_(data
, len
);
721 NOTIFY_ERROR("rc_data_new_('%s', %zu) failed", data
, len
);
726 while (*clist_iter
) {
727 struct evbuffer
*output
= bufferevent_get_output((*clist_iter
)->bev
);
729 /* FIXME: if connection is still valid... */
731 /* FIXME: clang claims this is a use-after-free if evbuffer_add_reference fails, but I don't yet believe it. */
732 rc_data_ref_inc_(rc
);
733 if (evbuffer_add_reference(output
, rc
->data
, rc
->data_len
, rc_cleanup_cb_
, rc
)) {
734 NOTIFY_ERROR("%s:%s", "evbuffer_add_reference", "failed");
742 /* FIXME: clang also claims this is use-after-free, same as above. */
750 connection_printf_broadcast(struct connection
*sender
, bool exclude_sender
, const char *fmt
, ...)
752 struct connection
**clist
;
753 char *message
= NULL
;
754 ssize_t message_len
= 0;
759 message_len
= vsnprintf(message
, message_len
, fmt
, ap
);
761 if (message_len
< 0) {
762 NOTIFY_ERROR("%s:%s (%zd)", "vsnprintf", strerror(errno
), message_len
);
766 message
= malloc(message_len
);
768 NOTIFY_ERROR("%s(%zd):%s", "malloc", message_len
, strerror(errno
));
772 message_len
= vsnprintf(message
, message_len
, fmt
, ap
);
774 if (message_len
< 0) {
775 NOTIFY_ERROR("%s:%s (%zd)", "vsnprintf", strerror(errno
), message_len
);
781 NOTIFY_DEBUG("message_len:%zu message:%s", message_len
, message
);
784 clist
= connections_all_as_array(&sender
->server
->connections
, exclude_sender
== true ? sender
: NULL
);
785 r
= connection_multi_send(clist
, message
, message_len
);
787 NOTIFY_ERROR("%s:%s", "connection_multi_send", "failed");
789 for (struct connection
**clist_iter
= clist
; *clist_iter
; clist_iter
++) {
790 connection_free(*clist_iter
);
801 connection_inc_ref(struct connection
*c
)
805 if ( (r
= pthread_mutex_lock(&c
->rc_mutex
)) ) {
806 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
810 c
->reference_count
+= 1;
813 NOTIFY_DEBUG("c:%p new reference_count:%zu", c
, c
->reference_count
);
816 if ( (r
= pthread_mutex_unlock(&c
->rc_mutex
)) ) {
817 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
823 * Frees a connection (and all data owned by a connection) if there are no more references to it.
826 connection_free(struct connection
*c
)
828 struct evbuffer
*tmp
;
832 if ( (r
= pthread_mutex_lock(&c
->rc_mutex
)) ) {
833 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
837 c
->reference_count
-= 1;
840 NOTIFY_DEBUG("c:%p new reference_count:%zu", c
, c
->reference_count
);
843 if (c
->reference_count
> 0) {
844 if ( (r
= pthread_mutex_unlock(&c
->rc_mutex
)) ) {
845 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
851 NOTIFY_DEBUG("c:%p freeing", c
);
853 connections_remove(c
);
855 bufferevent_disable(c
->bev
, EV_READ
|EV_WRITE
);
856 tmp
= bufferevent_get_output(c
->bev
);
857 evbuffer_drain(tmp
, evbuffer_get_length(tmp
));
858 tmp
= bufferevent_get_input(c
->bev
);
859 evbuffer_drain(tmp
, evbuffer_get_length(tmp
));
861 fd
= bufferevent_getfd(c
->bev
);
863 EVUTIL_CLOSESOCKET(fd
);
864 bufferevent_setfd(c
->bev
, -1);
867 if (c
->dns_retry_ev
) {
868 if (event_del(c
->dns_retry_ev
)) {
869 NOTIFY_ERROR("%s:%s", "event_del", strerror(errno
));
871 c
->dns_retry_ev
= NULL
;
874 if (c
->evdns_request
) {
875 evdns_cancel_request(c
->server
->evdns_base
, c
->evdns_request
);
876 c
->evdns_request
= NULL
;
879 if (c
->client_address
) {
880 free(c
->client_address
);
881 c
->client_address
= NULL
;
884 bufferevent_free(c
->bev
);
889 rc
= uuid_destroy(c
->uuid
);
890 if (rc
!= UUID_RC_OK
) {
891 NOTIFY_ERROR("%s:%s", "uuid_destroy", uuid_error(rc
));
896 /* empty out the command queue */
897 if ( (r
= pthread_mutex_lock(&c
->commands_mutex
)) ) {
898 NOTIFY_ERROR("%s:%s", "pthread_mutex_lock", strerror(r
));
901 struct command
*c1
, *c2
;
902 c1
= STAILQ_FIRST(&c
->commands_head
);
904 c2
= STAILQ_NEXT(c1
, stailq_entry
);
908 STAILQ_INIT(&c
->commands_head
);
910 if ( (r
= pthread_mutex_unlock(&c
->commands_mutex
)) ) {
911 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
913 if ( (r
= pthread_mutex_destroy(&c
->commands_mutex
)) ) {
914 NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r
));
917 if ( (r
= pthread_mutex_unlock(&c
->rc_mutex
)) ) {
918 NOTIFY_ERROR("%s:%s", "pthread_mutex_unlock", strerror(r
));
920 if ( (r
= pthread_mutex_destroy(&c
->rc_mutex
)) ) {
921 NOTIFY_ERROR("%s:%s", "pthread_mutex_destroy", strerror(r
));
924 memset(c
, 0, sizeof *c
);