event_request_t *event_add( context_t *ctx, const long fd, unsigned int flags ) { event_request_t *entry = event_find( ctx, fd, flags ); //x_printf(ctx, "EVENT ADD called. fd = %ld, flags = %02x (%c)\n",fd,flags,flags&EH_READ?'r':(flags&EH_WRITE?'w':(flags&EH_SPECIAL?'S':'?'))); if( !entry ) entry = event_find_free_slot(); if( ! entry ) return 0L; entry->fd = fd; entry->flags |= flags; entry->ctx = ctx; if( flags & EH_SIGNAL ) { // For signals, make sure the handler is installed if( ! sigismember( &event_signals.event_signal_mask, (int)fd ) ) { struct sigaction action_handler; memset( &action_handler, 0, sizeof( action_handler ) ); action_handler.sa_handler = handle_signal_event; action_handler.sa_flags = 0; // SA_RESTART; sigaction( (int)fd, &action_handler, NULL ); sigaddset( &event_signals.event_signal_mask, (int) fd ); sigdelset( &event_signals.event_signal_default, (int) fd ); } } else if( flags & EH_WANT_TICK ) { entry->timestamp = rel_time(0L); } else // For un-special file descriptors, force non-blocking if( (flags & (EH_READ|EH_WRITE)) && ! ( flags & EH_SPECIAL ) ) fcntl((int)fd, F_SETFL, fcntl((int)fd, F_GETFL) | O_NONBLOCK); return entry; }
ssize_t process_unicorn_packet( context_t *ctx ) { unicorn_config_t *cf = ctx->data; x_printf(ctx, "Entering process_unicorn_packet()\n"); size_t ready = u_ringbuf_ready( &cf->input ); if( !ready ) return -1; if( cf->flags & UNICORN_EXPECTING_DATA ) { return process_unicorn_data( ctx, ready ); } else { if( ready >= sizeof( frmHdr_t ) ) { u_ringbuf_read( &cf->input, & cf->msgHdr, sizeof( cf->msgHdr ) ); if( MAGIC_NUMBER != cf->msgHdr.magicNo ) { x_printf(ctx,"MAGIC NUMBER FAIL... tossing the baby out with the bathwater..\n"); u_ringbuf_init( &cf->input ); cf->flags &= ~(unsigned int)UNICORN_EXPECTING_DATA; x_printf(ctx, "Leaving process_unicorn_packet()\n"); return -1; } cf->last_message = rel_time(0L); x_printf(ctx, "Leaving process_unicorn_packet() via process_unicorn_header()\n"); return process_unicorn_header( ctx ); } } x_printf(ctx, "Leaving process_unicorn_packet()\n"); // return because there is nothing useful in the buffer.. return -1; }
ssize_t send_unicorn_command( context_t *ctx, cmdHost_t cmd, cmdState_t state, size_t length, void *data ) { unicorn_config_t *cf = ctx->data; driver_data_t notification = { TYPE_DATA, ctx, {} }; frmHdr_t *frame = alloca( sizeof( frmHdr_t ) + length ); if( !frame ) return -1; frame->magicNo = MAGIC_NUMBER; frame->cmd = cmd; frame->state = state; frame->length = (uint16_t) length; if( length ) memmove( &frame[1], data, length ); notification.event_data.data = frame; notification.event_data.bytes = sizeof( frmHdr_t ) + length; #ifdef KEEPALIVE_INCLUDES_OUTGOING cf->last_message = rel_time(0L); #endif return emit( cf->modem, EVENT_DATA_OUTGOING, ¬ification ); }
steady_time_point(std::chrono::time_point<Clock, Duration> const& std_abs_time) { boost::chrono::nanoseconds rel_time( std::chrono::duration_cast<std::chrono::nanoseconds>( std_abs_time - Clock::now()).count()); _abs_time = boost::chrono::steady_clock::now() + rel_time; }
int handle_event_set(fd_set * readfds, fd_set * writefds, fd_set * exceptfds) { time_t now = rel_time(0L); int i; for (i = 0; i < MAX_EVENT_REQUESTS; i++) { if (event_table[i].flags && ((event_table[i].flags & EH_SPECIAL) == 0)) { driver_data_t data = { TYPE_FD, 0L, {} }; data.event_request.fd = event_table[i].fd; data.event_request.flags = event_table[i].flags; if ((event_table[i].flags & EH_EXCEPTION) && FD_ISSET((unsigned int)event_table[i].fd, exceptfds)) { //d_printf("Exception event for %s\n",event_table[i].ctx->name); event_table[i].ctx->driver->emit(event_table[i].ctx, EVENT_EXCEPTION, &data); } if ((event_table[i].flags & EH_WRITE) && FD_ISSET((unsigned int)event_table[i].fd, writefds)) { //d_printf("Write event for %s\n",event_table[i].ctx->name); event_table[i].ctx->driver->emit(event_table[i].ctx, EVENT_WRITE, &data); } if ((event_table[i].flags & EH_READ) && FD_ISSET((unsigned int)event_table[i].fd, readfds)) { //d_printf("Read event for %s\n",event_table[i].ctx->name); event_table[i].ctx->driver->emit(event_table[i].ctx, EVENT_READ, &data); } } else if (event_table[i].flags & EH_TIMER) { if (alarm_table[event_table[i].fd].event_time <= now) { alarm_table[event_table[i].fd].flags |= ALARM_FIRED; //d_printf("Alarm marked as FIRED...\n"); driver_data_t data = { TYPE_ALARM, 0, {} }; data.event_alarm = (int) event_table[i].fd; event_table[i].ctx->driver->emit(event_table[i].ctx, EVENT_ALARM, &data); // If the event handler changes the alarm in any way, the 'fired' flag is cleared if (alarm_table[event_table[i].fd].flags & ALARM_FIRED) { //d_printf("ALARM event for %s\n",event_table[i].ctx->name); handle_event_alarm(&event_table[i]); } } } } return 0; }
int unicorn_init(context_t * ctx) { unicorn_config_t *cf ; if (0 == (cf = (unicorn_config_t *) calloc( sizeof( unicorn_config_t ) , 1 ))) return 0; cf->pending_action_timeout = cf->last_message = rel_time(0L); cf->driver_state = CMD_ST_UNKNOWN; u_ringbuf_init(&cf->input); ctx->data = cf; return 1; }
int handle_timer_events() { int i; driver_data_t tick = { TYPE_TICK, 0L, {} }; tick.event_tick = rel_time(0L); //d_printf("Checking tick requests..\n"); // All drivers get the same 'tick' timestamp, even if some drivers take time to process the tick for(i = 0; i < MAX_EVENT_REQUESTS; i++ ) if( ((event_table[i].flags & EH_WANT_TICK) == EH_WANT_TICK) && ((event_table[i].flags & EH_SPECIAL) == 0 )) { //d_printf("event[%d] Delivering tick to %s if %ld > %ld\n",i, event_table[i].ctx->name, tick.event_tick, event_table[i].timestamp ); if( tick.event_tick > event_table[i].timestamp ) { event_table[i].ctx->driver->emit( event_table[i].ctx, EVENT_TICK, &tick ); event_table[i].timestamp = tick.event_tick + (time_t) event_table[i].fd; } } return 0; }
int event_loop( long timeout ) { long max_fd = 0; fd_set fds_read, fds_write, fds_exception; if( ! create_event_set( &fds_read, &fds_write, &fds_exception, &max_fd ) ) return -1; time_t now = rel_time(0L); long alarm_time = alarm_getnext(); // Reduce 'timeout' to ensure the next scheduled alarm occurs on time if( alarm_time >= 0 ) { if( alarm_time > now ) { if( timeout > ( alarm_time - now )) timeout = alarm_time - now; } else timeout = 0; } #ifdef USE_PSELECT // If I know there are signals which need processing, reset the select timeout to 0 // Signals are normally blocked, so these would be manufactured events. struct timespec tm = { timeout / 1000, (timeout % 1000) * 1000 * 1000 }; if( event_signals.event_signal_pending_count ) tm.tv_nsec = tm.tv_sec = 0; int rc = pselect( (int) max_fd, &fds_read, &fds_write, &fds_exception, &tm, &event_signals.event_signal_default ); #else // expect queued signals to occur immediately sigprocmask( SIG_SETMASK, &event_signals.event_signal_default, NULL ); sleep(0); if( event_signals.event_signal_pending_count ) timeout = 0; struct timeval tm = { timeout / 1000, (timeout % 1000) * 1000 }; #if 0 #ifndef NDEBUG int i = 0; printf("READ: "); for(i=0;i<64;i++) if(FD_ISSET(i,&fds_read)) { printf("%d ",i); if( fcntl( i, F_GETFL ) < 0 ) printf("*"); } printf("WRITE: "); for(i=0;i<64;i++) if(FD_ISSET(i,&fds_write)) { printf("%d ",i); if( fcntl( i, F_GETFL ) < 0 ) printf("*"); } printf("EXCEPTION: "); for(i=0;i<64;i++) if(FD_ISSET(i,&fds_exception)) { printf("%d ",i); if( fcntl( i, F_GETFL ) < 0 ) printf("*"); } printf("\n"); #endif #endif #ifndef NDEBUG //printf("calling select...");fflush(stdout); #endif int rc = select( max_fd, &fds_read, &fds_write, &fds_exception, &tm ); #ifndef NDEBUG //printf("done\n");fflush(stdout); #endif #endif sigprocmask(SIG_BLOCK, &event_signals.event_signal_mask, NULL); if( rc < 0 ) { if( (errno != EINTR) #ifdef mips && (errno != ENOENT) #endif ) { d_printf("(p)select returned %d (errno = %d - %s)\n",rc, errno, strerror(errno)); exit(0); return -1; } else { return 0; // timeout - nothing to do } } if( event_signals.event_signal_pending_count ) { //d_printf("Calling handle_pending_signals()\n"); handle_pending_signals(); } //d_printf("Calling handle_event_set()\n"); rc = handle_event_set( &fds_read, &fds_write, &fds_exception ); if( !rc ) { //d_printf("Calling handle_timer_events()\n"); handle_timer_events(); } return 0; }
ssize_t unicorn_handler(context_t *ctx, event_t event, driver_data_t *event_data) { event_data_t *data = 0L; event_child_t *child = 0L; unicorn_config_t *cf = (unicorn_config_t *) ctx->data; //x_printf(ctx, "<%s> Event = \"%s\" (%d)\n", ctx->name, event_map[event], event); if (event_data->type == TYPE_DATA) data = &event_data->event_data; else if( event_data->type == TYPE_CHILD ) child = & event_data->event_child; switch (event) { case EVENT_INIT: { x_printf(ctx,"calling event add SIGQUIT\n"); event_add( ctx, SIGQUIT, EH_SIGNAL ); x_printf(ctx,"calling event add SIGTERM\n"); event_add( ctx, SIGTERM, EH_SIGNAL ); x_printf(ctx,"calling event 1000 EH_WANT_TICK\n"); event_add( ctx, 1000, EH_WANT_TICK ); cf->driver = config_get_item( ctx->config, "endpoint" ); if( ! config_get_timeval( ctx->config, "retry", &cf->retry_time ) ) cf->retry_time = 120*1000; if( cf->driver ) start_service( &cf->modem, cf->driver, ctx->config, ctx, 0L ); if( !cf->modem ) { logger( ctx, "Unable to launch modem driver. Exiting\n" ); cf->state = UNICORN_STATE_ERROR; context_terminate( ctx ); return -1; } cf->state = UNICORN_STATE_IDLE; } break; case EVENT_TERMINATE: { cf->pending_action_timeout = rel_time(0L); cf->flags |= UNICORN_TERMINATING; // In process of terminating the modem driver cf->state = UNICORN_STATE_STOPPING; // In process of terminating self // Ensure 'exec' driver known not to restart when the modem driver terminates. // If the modem driver is something other than 'exec', this should be ignored. uint8_t flag = 0; driver_data_t notification = { TYPE_CUSTOM, ctx, {} }; notification.event_custom = &flag; emit(cf->modem, EXEC_SET_RESPAWN, ¬ification); if( cf->driver_state == CMD_ST_ONLINE ) { x_printf(ctx,"Driver is online - sending disconnect\n"); send_unicorn_command( ctx, CMD_DISCONNECT, CMD_ST_OFFLINE, 0, 0L ); } else { x_printf(ctx,"Driver is offline - sending shutdown\n"); send_unicorn_command( ctx, CMD_STATE, CMD_ST_OFFLINE, 0, 0L ); } } break; case EVENT_RESTART: // This event is used to signal that the modem driver needs to resync. // set the 'reconnecting' flag and send a disconnect x_printf(ctx,"EVENT_RESTART: - sending disconnect to modem\n"); //logger(ctx, "Sending disconnect command to modem driver"); if( event_data->source == ctx->owner ) { cf->pending_action_timeout = rel_time(0L); cf->flags |= UNICORN_WAITING_FOR_CONNECT; if( cf->modem ) { x_printf(ctx, "Sending CMD_DISCONNECT to modem driver (%s)\n",cf->modem->name); if( (event_data->type == TYPE_CUSTOM) && event_data->event_custom ) { logger(ctx, "Sending abort command to modem driver due to unexpected disconnect"); send_unicorn_command( ctx, CMD_ABORT, CMD_ST_OFFLINE, 0, 0L ); } else { logger(ctx, "Sending disconnect command to modem driver"); send_unicorn_command( ctx, CMD_DISCONNECT, CMD_ST_OFFLINE, 0, 0L ); } } else { x_printf(ctx, "Modem driver not running.. doing nothing.\n"); } } else { x_printf(ctx,"Forwarding EVENT_RESTART to owner (%s)\n",ctx->name); emit2( ctx, EVENT_RESTART, event_data); } break; case EVENT_CHILD: x_printf(ctx,"Got a message from a child (%s:%d).. probably starting\n", child->ctx->name, child->action); if ( child->ctx == cf->modem ) { if( child->action == CHILD_STARTING ) { cf->state = UNICORN_STATE_RUNNING; cf->flags &= ~(unsigned int) UNICORN_RESTARTING; // Assume ensure the first time the modem driver starts it skips the connection delay cf->flags |= UNICORN_FIRST_START; } if ( child->action == CHILD_STOPPED ) { x_printf(ctx,"Modem driver terminated - restart or terminate\n"); // modem driver terminated. Restart or exit. cf->state = UNICORN_STATE_IDLE; if ( cf->flags & UNICORN_TERMINATING ) { x_printf(ctx,"Terminating immediately\n"); context_terminate( ctx ); } else { x_printf(ctx,"Need to restart modem driver\n"); cf->flags |= UNICORN_RESTARTING; cf->pending_action_timeout = rel_time(0L); // Reset the driver state, and notify the parent that we are offline cf->driver_state = CMD_ST_UNKNOWN; context_owner_notify( ctx, CHILD_EVENT, UNICORN_MODE_OFFLINE ); } } } break; case EVENT_DATA_INCOMING: case EVENT_DATA_OUTGOING: if( event_data->source == cf->modem ) { size_t bytes = data->bytes; size_t offset = 0; while( bytes ) { size_t to_read = u_ringbuf_avail( &cf->input ); if( to_read > bytes ) to_read = bytes; u_ringbuf_write( &cf->input, &((char *)data->data)[offset], to_read ); bytes -= to_read; offset += to_read; while(process_unicorn_packet(ctx) >= 0); } return (ssize_t) offset; } else { send_unicorn_command( ctx, CMD_DATA, CMD_ST_ONLINE, data->bytes,data->data); return (ssize_t) data->bytes; } break; case EVENT_READ: break; case EVENT_EXCEPTION: break; case EVENT_SIGNAL: x_printf(ctx,"Woa! Got a sign from the gods... %d\n", event_data->event_signal); if( event_data->event_signal == SIGQUIT || event_data->event_signal == SIGTERM ) emit( ctx, EVENT_TERMINATE, 0L ); break; case EVENT_TICK: { time_t now = rel_time(0L); // Handle case where a massive time shift due to NTP resync causes all timeouts to fire simultaneously // This is technically deprecated due to the use of rel_time() if( (now - cf->last_message) > MAXIMUM_SAFE_TIMEDELTA ) { logger(ctx, "WARNING: Resetting timeout due to RTC time change"); cf->last_message = now; } if( ((now - cf->last_message) > UNICORN_KEEPALIVE_TIMEOUT ) && ( cf->driver_state != CMD_ST_UNKNOWN )) { if( ~ cf->flags & UNICORN_LAGGED ) { // Its been a couple of minutes since the last keepalive, reset the driver_state // to unknown and prompt for one. logger(ctx,"Forcing connection state request due to communications timeout.\n"); cf->flags |= UNICORN_LAGGED; cf->retry_count = UNICORN_KEEPALIVE_RETRY_MAX; } if( cf->retry_count ) { send_unicorn_command( ctx, CMD_STATE, CMD_ST_OFFLINE, 0, 0L ); cf->retry_count --; } else { // Its been a long time since the last message, despite prompting for one // restart the modem driver logger(ctx, "Communications timeout. Restarting modem driver."); uint8_t sig = SIGHUP; driver_data_t notification = { TYPE_CUSTOM, ctx, {} }; notification.event_custom = &sig; emit( cf->modem, EVENT_RESTART, ¬ification ); } cf->last_message = now; } if( (cf->flags & UNICORN_RESTARTING) && ((now - cf->pending_action_timeout) > UNICORN_RESTART_DELAY )) { x_printf(ctx,"Restart delay expired - restarting modem driver\n"); cf->pending_action_timeout = rel_time(0L); if( cf->driver ) start_service( &cf->modem, cf->driver, ctx->config, ctx, 0L ); } else if( (cf->flags & UNICORN_RECONNECTING) && ((now - cf->pending_action_timeout) > cf->retry_time )) { x_printf(ctx,"Reconnect delay expired - attempting reconnect\n"); cf->pending_action_timeout = rel_time(0L); cf->flags &= ~(unsigned int)UNICORN_RECONNECTING; if( cf->modem ) send_unicorn_command(ctx, CMD_CONNECT, CMD_ST_ONLINE, 0, 0 ); } else if( (cf->flags & UNICORN_WAITING_FOR_CONNECT) && ((now - cf->pending_action_timeout) > UNICORN_CONNECT_TIMEOUT )) { x_printf(ctx,"Timeout during connect - terminating modem driver\n"); cf->flags &= ~(unsigned int) UNICORN_WAITING_FOR_CONNECT; cf->state = UNICORN_STATE_IDLE; if( cf->modem ) emit( cf->modem, EVENT_TERMINATE, 0L ); } if( (cf->flags & UNICORN_TERMINATING) && ((now - cf->pending_action_timeout) > UNICORN_PROCESS_TERMINATION_TIMEOUT)) { x_printf(ctx,"termination timeout - killing the modem driver with prejudice\n"); cf->state = UNICORN_STATE_IDLE; if( cf->modem ) context_terminate( cf->modem ); context_terminate( ctx ); } // Special case.. If I am expecting a data frame, and it takes too long to arrive, // reset state. if( (cf->flags & UNICORN_EXPECTING_DATA) && ((now - cf->last_message) > FRAME_TIMEOUT)) { x_printf(ctx,"FRAME TIMEOUT - resetting input buffer\n"); u_ringbuf_init( &cf->input ); cf->flags &= ~(unsigned int)UNICORN_EXPECTING_DATA; } #ifndef NDEBUG size_t bytes = u_ringbuf_ready( &cf->input ); if( bytes ) x_printf(ctx,"Un-processed data in ring buffer... %d bytes\n",(int)bytes); #endif } break; default: x_printf(ctx,"\n *\n *\n * Emitted some kind of event \"%s\" (%d)\n *\n *\n", event_map[event], event); } return 0; }
ssize_t process_unicorn_header( context_t *ctx ) { unicorn_config_t *cf = ctx->data; switch( cf->msgHdr.cmd ) { case CMD_READY: // If the driver has JUST STARTED, then it bring the network online immediately, otherwise // the retry delay takes effect cf->flags |= UNICORN_FIRST_START; cf->driver_state = CMD_ST_UNKNOWN; case CMD_KEEPALIVE: case CMD_STATE: cf->flags &= ~(unsigned int)UNICORN_LAGGED; if( cf->msgHdr.state != cf->driver_state ) { if( cf->msgHdr.state == CMD_ST_ONLINE || cf->msgHdr.state == CMD_ST_OFFLINE || cf->msgHdr.state == CMD_ST_ERROR ) { x_printf(ctx,"Notifying parent that the network state has changed to %s\n",cf->msgHdr.state == CMD_ST_ONLINE ? "online":"offline"); context_owner_notify( ctx, CHILD_EVENT, (child_status_t) (cf->msgHdr.state == CMD_ST_ONLINE ? UNICORN_MODE_ONLINE : UNICORN_MODE_OFFLINE) ); } if( cf->flags & UNICORN_TERMINATING ) { x_printf(ctx,"State = TERMINATING, need to shutdown driver\n"); if( cf->msgHdr.state == CMD_ST_OFFLINE ) send_unicorn_command( ctx, CMD_SHUTDOWN, CMD_ST_OFFLINE, 0, 0L ); else send_unicorn_command( ctx, CMD_DISCONNECT, CMD_ST_OFFLINE, 0, 0L ); } else { switch( cf->msgHdr.state ) { case CMD_ST_ONLINE: x_printf(ctx,"State is Online - resetting WAITING_FOR_CONNECT/RECONNECTING status\n"); cf->flags &= ~(unsigned int) (UNICORN_WAITING_FOR_CONNECT|UNICORN_RECONNECTING|UNICORN_FIRST_START); break; case CMD_ST_OFFLINE: x_printf(ctx,"Sending CONNECT command (Setting WAITING_FOR_CONNECT/RECONNECTING FLAG)\n"); // RECONNECTING causes a CMD_CONNECT to be sent after the RETRY delay // WAITING_FOR_CONNECT causes the timeout handler to kill the modem driver if a // connect does not occur before a specified window of time has elapsed cf->pending_action_timeout = cf->last_message; // Special case, in a first start scenario, don't wait retry if( cf->flags & UNICORN_FIRST_START) { x_printf(ctx,"Forcing connection - FIRST_START is set\n"); send_unicorn_command(ctx, CMD_CONNECT, CMD_ST_ONLINE, 0, 0 ); cf->flags &= ~(unsigned int) (UNICORN_FIRST_START); cf->flags |= UNICORN_WAITING_FOR_CONNECT; } else { x_printf(ctx,"Setting up for delayed connection\n"); cf->flags |= UNICORN_WAITING_FOR_CONNECT|UNICORN_RECONNECTING; } break; case CMD_ST_ERROR: break; default: break; } } cf->driver_state = cf->msgHdr.state; } break; case CMD_DATA: cf->flags |= UNICORN_EXPECTING_DATA; cf->data_length = cf->msgHdr.length; cf->last_message = rel_time(0L); break; default: x_printf(ctx,"Not ready to deal with cmd %d\n",cf->msgHdr.cmd); break; } return 0; }
void logger::run() { utxx::signal_block block_signals(m_block_signals); if (m_on_before_run) m_on_before_run(); if (!m_ident.empty()) pthread_setname_np(pthread_self(), m_ident.c_str()); int event_val; do { event_val = m_event.value(); //wakeup_result rc = wakeup_result::TIMEDOUT; while (!m_abort && m_queue.empty()) { m_event.wait(&m_wait_timeout, &event_val); ASYNC_DEBUG_TRACE( (" %s LOGGER awakened (res=%s, val=%d, futex=%d), abort=%d, head=%s\n", timestamp::to_string().c_str(), to_string(rc).c_str(), event_val, m_event.value(), m_abort, m_queue.empty() ? "empty" : "data") ); } // When running with maximum priority, occasionally excessive use of // sched_yield may use to system slowdown, so this option is // configurable by m_sched_yield_us: if (m_queue.empty() && m_sched_yield_us >= 0) { time_val deadline(rel_time(0, m_sched_yield_us)); while (m_queue.empty()) { if (m_abort) goto DONE; if (now_utc() > deadline) break; sched_yield(); } } // Get all pending items from the queue for (auto* item = m_queue.pop_all(), *next=item; item; item = next) { next = item->next(); try { dolog_msg(item->data()); } catch ( std::exception const& e ) { // Unhandled error writing data to some destination // Print error report to stderr (can't do anything better -- // the error happened in the m_on_error callback!) const msg msg(LEVEL_INFO, "", std::string("Fatal exception in logger"), UTXX_LOG_SRCINFO); detail::basic_buffered_print<1024> buf; char pfx[256], sfx[256]; char* p = format_header(msg, pfx, pfx + sizeof(pfx)); char* q = format_footer(msg, sfx, sfx + sizeof(sfx)); auto ps = p - pfx; auto qs = q - sfx; buf.reserve(msg.m_fun.str.size() + ps + qs + 1); buf.sprint(pfx, ps); buf.print(msg.m_fun.str); buf.sprint(sfx, qs); std::cerr << buf.str() << std::endl; m_abort = true; // TODO: implement attempt to store transient messages to some // other medium // Free all pending messages while (item) { m_queue.free(item); item = next; next = item->next(); } goto DONE; } m_queue.free(item); item = next; } } while (!m_abort); DONE: if (!m_silent_finish) { const msg msg(LEVEL_INFO, "", std::string("Logger thread finished"), UTXX_LOG_SRCINFO); try { dolog_msg(msg); } catch (...) {} } if (m_on_after_run) m_on_after_run(); }