/** ======================================================================== * * @param source * * @return TRUE if timer has expired, FALSE if not. * * ========================================================================= */ static gboolean multi_fd_watch_is_timer_expired(PslMultiFdWatchSource* source) { PSL_LOG_DEBUGLOW("%s (watch=%p)", __func__, source); PSL_ASSERT(!source->restartTimer); if (MULTI_FD_WATCH_INFINITE_MSEC == source->timeoutMillisec) { PSL_LOG_DEBUGLOW("%s (watch=%p): using infinite timeout", __func__, source); return false; } struct timespec now; if (!psl_time_get_current_mono_time(&now)) { return false; } if (psl_time_compare_timespecs(&now, &source->when) >= 0) { return true; } else { return false; } }
/** ======================================================================== * chan_fsm_destroy_widgets(): Called by chan_fsm_free() and the * PslChanFsmFinalState's kFsmEventEnterScope event handler to * destroy the "widgets" used the the FSM. * * @note See the note about deadlock work-around in * psl_chan_fsm_finalize() header comments. * * @param pFsm * ========================================================================= */ static void chan_fsm_destroy_widgets(PslChanFsm* const pFsm) { PSL_LOG_INFO("%s (fsm=%p)", __func__, pFsm); PSL_ASSERT(pFsm); if (pFsm->fdWatchInfo.fdWatch) { /** * @note Multi-level GSource arrangements such as ours (a * GSource that monitors our channel instance and another * GSource owned by the channel instance) are susceptible * to deadlock in glib's gmain code if we're not careful. * We use lots of low-level logging below to help with * debugging should a deadlock be triggered. */ (void)psl_multi_fd_watch_reset(pFsm->fdWatchInfo.fdWatch); PSL_LOG_DEBUGLOW("%s (fsm=%p): Detaching multi-fd-watch=%p", __func__, pFsm, pFsm->fdWatchInfo.fdWatch); g_source_destroy((GSource*)pFsm->fdWatchInfo.fdWatch); PSL_LOG_DEBUGLOW("%s (fsm=%p): Finished detaching multi-fd-watch=%p", __func__, pFsm, pFsm->fdWatchInfo.fdWatch); PSL_LOG_DEBUGLOW("%s (fsm=%p): Unref'ing multi-fd-watch=%p", __func__, pFsm, pFsm->fdWatchInfo.fdWatch); g_source_unref((GSource*)pFsm->fdWatchInfo.fdWatch); PSL_LOG_DEBUGLOW("%s (fsm=%p): Finished unref'ing multi-fd-watch=%p", __func__, pFsm, pFsm->fdWatchInfo.fdWatch); pFsm->fdWatchInfo.fdWatch = NULL; } g_free(pFsm->userSettings.bindAddr.hostStr); pFsm->userSettings.bindAddr.hostStr = NULL; g_free(pFsm->userSettings.serverAddr.hostStr); pFsm->userSettings.serverAddr.hostStr = NULL; if (pFsm->userSettings.threadCtx) { PmSockThreadCtxUnref(pFsm->userSettings.threadCtx); pFsm->userSettings.threadCtx = NULL; } PSL_LOG_DEBUGLOW("%s (fsm=%p): LEAVING", __func__, pFsm); }
/** ======================================================================== * ========================================================================= */ static void chan_fsm_free(PslChanFsm* const pFsm) { PSL_LOG_INFO("%s (fsm=%p)", __func__, pFsm); PSL_ASSERT(pFsm); /** * If the FSM was successfully started, then we should have been * finalized by the channel via psl_chan_fsm_finalize() before * our reference count dropped to zero */ PSL_ASSERT(!pFsm->fsmStarted || pFsm->fsmFinalized); /// Our sock should be closed by the state handlers if (pFsm->fd >= 0) { PSL_LOG_ERROR("%s: ERROR: pFsm->fd not closed!", __func__); } if (!pFsm->fsmStarted) { chan_fsm_destroy_widgets(pFsm); } g_free(pFsm); PSL_LOG_DEBUGLOW("%s (fsm=%p): LEAVING", __func__, pFsm); }
/** ======================================================================== * ========================================================================= */ PslError psl_multi_fd_watch_reset(PslMultiFdWatchSource* source) { PSL_LOG_DEBUG("%s (watch=%p): ENTERING", __func__, source); source->restartTimer = true; source->timeoutMillisec = MULTI_FD_WATCH_INFINITE_MSEC; GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, source->descTable); while (g_hash_table_iter_next(&iter, &key, &value)) { PslMultiFdDescriptor* desc = (PslMultiFdDescriptor*)value; const gint fd = GPOINTER_TO_INT(key); PSL_ASSERT(fd == desc->pollFd.fd); if (desc->pollAdded) { g_source_remove_poll((GSource*)source, &desc->pollFd); } g_hash_table_iter_remove(&iter); } PSL_LOG_DEBUGLOW("%s (watch=%p): LEAVING", __func__, source); return 0; }
/** ======================================================================== * ========================================================================= */ PslError psl_multi_fd_watch_remove_fd(PslMultiFdWatchSource* source, int const fd) { gboolean removed; PslMultiFdDescriptor* desc; PSL_LOG_DEBUGLOW("%s (watch=%p), fd=%d", __func__, source, (int)fd); desc = (PslMultiFdDescriptor*) g_hash_table_lookup(source->descTable, GINT_TO_POINTER(fd)); if (!desc) { PSL_LOG_ERROR("%s (watch=%p): ERROR: fd=%d not found", __func__, source, (int)fd); return PSL_ERR_INVAL; } if (desc->pollAdded) { g_source_remove_poll((GSource*)source, &desc->pollFd); } removed = g_hash_table_remove(source->descTable, GINT_TO_POINTER(fd)); PSL_ASSERT(removed); return 0; }
/** ======================================================================== * ========================================================================= */ int psl_multi_fd_watch_get_fd_count(PslMultiFdWatchSource* source) { PSL_LOG_DEBUGLOW("%s (watch=%p)", __func__, source); return g_hash_table_size (source->descTable); }
/** ======================================================================== * Update the timeout expiry time * * @param source * * @return gint: number of milliseconds that remain, or * MULTI_FD_WATCH_INFINITE_MSEC if infinite * * ========================================================================= */ static gint multi_fd_watch_update_expiry_time(PslMultiFdWatchSource* source) { if (MULTI_FD_WATCH_INFINITE_MSEC == source->timeoutMillisec) { PSL_LOG_DEBUGLOW("%s (watch=%p): using infinite timeout", __func__, source); source->restartTimer = false; return MULTI_FD_WATCH_INFINITE_MSEC; } gint remainingMillisec; if (source->restartTimer) { source->restartTimer = false; psl_time_get_current_mono_time(&source->when); psl_time_add_millisec_to_timespec(&source->when, source->timeoutMillisec); remainingMillisec = source->timeoutMillisec; } else { struct timespec time; psl_time_get_current_mono_time(&time); bool wouldUnderflow; /// 'time - when' psl_time_subtract_timespec_abs(&time, &source->when, &wouldUnderflow); /// 'time' is now the absolute value of 'time - when' if (wouldUnderflow) { /// expiry is still in the future remainingMillisec = psl_time_convert_timespec_to_millisec(&time); } else { remainingMillisec = 0; ///< already expired } } PSL_LOG_DEBUGLOW("%s (watch=%p): new mono expiry time: sec=%ld, nsec=%ld", __func__, source, (long)source->when.tv_sec, (long)source->when.tv_nsec); return remainingMillisec; }
/** ======================================================================== * ========================================================================= */ PslError psl_multi_fd_watch_add_or_update_fd(PslMultiFdWatchSource* const source, int const fd, GIOCondition const condition) { PslMultiFdDescriptor* desc; PSL_LOG_DEBUG("%s (watch=%p): fd=%d, GIOCondition=0x%lX", __func__, source, (int)fd, (unsigned long)condition); desc = (PslMultiFdDescriptor*) g_hash_table_lookup(source->descTable, GINT_TO_POINTER(fd)); /** * @note multi_fd_watch_prepare() takes care of adding/removing * our pollFd's from the source's poll list as needed */ if (desc) { PSL_LOG_DEBUGLOW( "%s (watch=%p): found desc=%p with fd=%d in descriptor table=%p", __func__, source, desc, (int)fd, source->descTable); desc->pollFd.events = condition; } else { /// create a new descriptor and add it to the poll set desc = g_new0(PslMultiFdDescriptor, 1); if (!desc) { PSL_LOG_FATAL("%s (watch=%p): ERROR: g_new0 failed", __func__, source); return PSL_ERR_MEM; } desc->pollFd.fd = fd; desc->pollFd.events = condition; g_hash_table_insert(source->descTable, GINT_TO_POINTER(desc->pollFd.fd), desc); PSL_LOG_DEBUGLOW( "%s (watch=%p): allocated desc=%p for fd=%d in descriptor table=%p", __func__, source, desc, (int)fd, source->descTable); } return 0; }
/** ======================================================================== * ========================================================================= */ bool psl_inet_ipaddr_from_string(const char* const hn, PslInetIPAddress* const pBuf, const char* const userLabel, const void* const cookie) { PSL_ASSERT(pBuf); pBuf->family = AF_UNSPEC; if (!hn || !*hn) { PSL_LOG_ERROR("%s (%s=%p): ERROR: NULL or empty hostname string", __func__, userLabel, cookie); return false; } int rc; rc = inet_pton(AF_INET, hn, &pBuf->addr); if (rc > 0) { PSL_LOG_DEBUGLOW("%s (%s=%p): hostname looks like IPv4 address", __func__, userLabel, cookie); pBuf->family = AF_INET; pBuf->len = sizeof(struct in_addr); return true; } else { rc = inet_pton(AF_INET6, hn, &pBuf->addr); if (rc > 0) { PSL_LOG_DEBUGLOW("%s (%s=%p): hostname looks like IPv6 address", __func__, userLabel, cookie); pBuf->family = AF_INET6; pBuf->len = sizeof(struct in6_addr); return true; } else { PSL_LOG_DEBUGLOW("%s (%s=%p): hostname string '%s' doesn't look " \ "like an IP address'.", __func__, userLabel, cookie, PSL_LOG_OBFUSCATE_STR(hn)); } } return false; }//psl_inet_ipaddr_from_string
/* ========================================================================= * ========================================================================= */ GMainContext* PmSockThreadCtxPeekGMainContext(PmSockThreadContext* const ctx) { PSL_ASSERT(ctx); PSL_LOG_DEBUGLOW("%s (ctx=%p/%s): gmainCtx=%p", __func__, ctx, ctx->userLabel, ctx->gmainCtx); return ctx->gmainCtx; }
/** ======================================================================== * The 'prepare' function of GSourceFuncs * * @param base * @param timeout @see GSourceFuncs * * @return gboolean @see GSourceFuncs * * ========================================================================= */ static gboolean multi_fd_watch_prepare(GSource* base, gint* timeout) { PSL_LOG_DEBUGLOW("%s (watch=%p)", __func__, base); PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base; /// Clear revents of our pollrecs GHashTableIter iter; gpointer key, value; g_hash_table_iter_init(&iter, source->descTable); while (g_hash_table_iter_next(&iter, &key, &value)) { PslMultiFdDescriptor* const desc = (PslMultiFdDescriptor*)value; desc->pollFd.revents = 0; if (desc->pollFd.events) { if (!desc->pollAdded) { g_source_add_poll(base, &desc->pollFd); desc->pollAdded = true; } } else if (desc->pollAdded) { g_source_remove_poll(base, &desc->pollFd); desc->pollAdded = false; } PSL_LOG_DEBUGLOW( "%s (watch=%p): iter: table=%p, desc=%p, fd=%d, " \ "monitoring GIOCondition=0x%lX", __func__, source, source->descTable, desc, (int)desc->pollFd.fd, (unsigned long)desc->pollFd.events); } /// Calc remaining timeout *timeout = multi_fd_watch_update_expiry_time(source); gboolean const readyNow = (0 == *timeout); PSL_LOG_DEBUGLOW("%s (watch=%p): %s", __func__, base, readyNow ? "READY" : "NOT READY"); return readyNow; }
/* ========================================================================= * ========================================================================= */ PmSockThreadContext* PmSockThreadCtxRef(PmSockThreadContext* const ctx) { PSL_ASSERT(ctx); PSL_LOG_DEBUGLOW("%s (ctx=%p/%s)", __func__, ctx, ctx->userLabel); psl_refcount_atomic_ref(&ctx->refCount_); return ctx; }
/** ======================================================================== * ========================================================================= */ PslChanFsm* psl_chan_fsm_ref(PslChanFsm* const pFsm) { PSL_LOG_DEBUGLOW("%s (fsm=%p)", __func__, pFsm); PSL_ASSERT(pFsm); psl_refcount_atomic_ref(&pFsm->refCount); return pFsm; }
/** ======================================================================== * ========================================================================= */ void psl_chan_fsm_unref(PslChanFsm* const pFsm) { PSL_LOG_DEBUGLOW("%s (fsm=%p)", __func__, pFsm); PSL_ASSERT(pFsm); PSL_ASSERT(!pFsm->base.inEvtDispatch && "MUST avoid FSM run-to-completion violation"); if (psl_refcount_atomic_unref(&pFsm->refCount)) { chan_fsm_free(pFsm); } }
/** ======================================================================== * The 'check' function of GSourceFuncs * * @param base * * @return gboolean @see GSourceFuncs * * ========================================================================= */ static gboolean multi_fd_watch_check(GSource* base) { PSL_LOG_DEBUGLOW("%s (watch=%p)", __func__, base); PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base; /// If the timer is ready, there is no need to check the fd's if (multi_fd_watch_is_timer_expired(source)) { PSL_LOG_DEBUGLOW("%s (watch=%p): READY: timeout expired", __func__, base); return true; } /// Check if any of the file descriptors is ready gpointer key, value; GHashTableIter iter; g_hash_table_iter_init(&iter, source->descTable); while (g_hash_table_iter_next(&iter, &key, &value)) { const GPollFD* const pollFd = &((PslMultiFdDescriptor*)value)->pollFd; PSL_LOG_DEBUGLOW( "%s (watch=%p): iter: table=%p, desc=%p, fd=%d, " \ "GIOCondition=(mon:0x%lX, ind:0x%lX)", __func__, source, source->descTable, value, (int)pollFd->fd, (unsigned long)pollFd->events, (unsigned long)pollFd->revents); if ((pollFd->revents & (pollFd->events | MULTI_FD_WATCH_PERM_FAILURE_IO_CONDS)) != 0) { PSL_LOG_DEBUGLOW("%s (watch=%p): fd=%d is READY", __func__, base, (int)pollFd->fd); return true; } } PSL_LOG_DEBUGLOW("%s (watch=%p): NOT READY", __func__, base); return false; }
/** ======================================================================== * ========================================================================= */ PslMultiFdWatchSource* psl_multi_fd_watch_new(void) { GSource* const base = g_source_new(&psl_multi_fd_watch_funcs, sizeof(PslMultiFdWatchSource)); PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base; if (!base) { goto error_cleanup; } /** * @note g_source_new returns a zero-initialized structure with * the base (GSource) "class" properly set up and with * reference count = 1. */ source->restartTimer = true; source->timeoutMillisec = MULTI_FD_WATCH_INFINITE_MSEC; source->descTable = g_hash_table_new_full(&g_direct_hash, &g_direct_equal, NULL /*key_destroy_func*/, &g_free/*value_destroy_func*/); if (!source->descTable) { goto error_cleanup; } PSL_LOG_DEBUGLOW("%s (watch=%p): descriptor table=%p", __func__, source, source->descTable); source->cachedReadyPollFdArray = g_array_new(false/*zero_terminated*/, false/*clear_*/, sizeof(PslMultiFdPollRec)); if (!source->cachedReadyPollFdArray) { goto error_cleanup; } PSL_LOG_DEBUG("%s (watch=%p): new watch created", __func__, source); return source; error_cleanup: PSL_LOG_FATAL("%s: FAILED (out of mem?)", __func__); if (base) { g_source_unref(base); } return NULL; }
/* ========================================================================= * ========================================================================= */ void PmSockThreadCtxUnref(PmSockThreadContext* const ctx) { PSL_ASSERT(ctx); PSL_LOG_DEBUGLOW("%s (ctx=%p/%s)", __func__, ctx, ctx->userLabel); if (!psl_refcount_atomic_unref (&ctx->refCount_)) { return; } thread_context_destroy_internal(ctx); }
/* ========================================================================= * ========================================================================= */ static void thread_context_destroy_internal(PmSockThreadContext* const ctx) { PSL_LOG_DEBUG("%s (ctx=%p/%s)", __func__, ctx, PSL_LOG_MAKE_SAFE_STR(ctx->userLabel)); PSL_ASSERT(ctx); g_free(ctx->userLabel); if (ctx->gmainCtx) { g_main_context_unref(ctx->gmainCtx); } g_free(ctx); PSL_LOG_DEBUGLOW("%s (ctx=%p): LEAVING", __func__, ctx); }
/** ======================================================================== * The 'dispatch' function of GSourceFuncs * * @param base * @param opaqueCb @see GSourceFuncs * @param userData @see GSourceFuncs * * @return gboolean @see GSourceFuncs * * ========================================================================= */ static gboolean multi_fd_watch_dispatch(GSource* const base, GSourceFunc opaqueCb, gpointer userData) { PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base; PslMultiFdWatchSourceCb* const cb = (PslMultiFdWatchSourceCb*)opaqueCb; source->restartTimer = true; ///< so it will restart at next poll prepare if (!cb) { PSL_LOG_ERROR("%s (watch=%p): ERROR: multi-fd watch dispatch with " \ "NULL user callback ptr: did you forget to call " \ "g_source_set_callback()?", __func__, base); return false; } PSL_LOG_DEBUGLOW("%s (watch=%p): preparing to call user's callback", __func__, base); /// Construct an array of ready PollFD's if (source->cachedReadyPollFdArray->len > 0) { g_array_set_size(source->cachedReadyPollFdArray, 0); } gpointer key, value; GHashTableIter iter; g_hash_table_iter_init(&iter, source->descTable); while (g_hash_table_iter_next(&iter, &key, &value)) { const GPollFD* const pollFd = &((PslMultiFdDescriptor*)value)->pollFd; PSL_LOG_DEBUGLOW( "%s (watch=%p): iter: table=%p, desc=%p, fd=%d, " \ "GIOCondition=(mon:0x%lX, ind:0x%lX)", __func__, source, source->descTable, value, (int)pollFd->fd, (unsigned long)pollFd->events, (unsigned long)pollFd->revents); if ((pollFd->revents & MULTI_FD_WATCH_PERM_FAILURE_IO_CONDS) != 0) { PSL_LOG_ERROR("%s: (watch=%p): I/O FAILURE on fd=%d: indicated " \ "GIOCondition=0x%lX", __func__, source, (int)pollFd->fd, (unsigned long)pollFd->revents); } const GIOCondition currentCondition = pollFd->revents & (pollFd->events | MULTI_FD_WATCH_PERM_FAILURE_IO_CONDS); if (currentCondition) { const PslMultiFdPollRec pollRec = { .fd = pollFd->fd, .reqEvents = pollFd->events, .indEvents = currentCondition }; g_array_append_vals(source->cachedReadyPollFdArray, &pollRec, 1); } } /// Dispatch the callback const gint numrecs = source->cachedReadyPollFdArray->len; const PslMultiFdPollRec* const pollrecs = ((numrecs > 0) ? (PslMultiFdPollRec*)source->cachedReadyPollFdArray->data : NULL); PSL_LOG_DEBUGLOW("%s (watch=%p): Calling user's callback: " \ "pollrec array=%p, numelts=%d", __func__, base, pollrecs, (int)numrecs); const gboolean stayAttached = cb(userData, pollrecs, numrecs); if (!stayAttached) { PSL_LOG_DEBUG("%s (watch=%p): user cb requested removal of source", __func__, base); } return stayAttached; } //multi_fd_watch_dispatch /** ======================================================================== * The 'finalize' function of GSourceFuncs * * @param base * * ========================================================================= */ static void multi_fd_watch_finalize(GSource* base) { PSL_LOG_DEBUG("%s (watch=%p)", __func__, base); PslMultiFdWatchSource* const source = (PslMultiFdWatchSource*)base; /** * @note We may be called with our own data members in a * partially-constructed (but initialized) state if * psl_multi_fd_watch_new failed part way through and * called g_source_unref. */ if (source->descTable) { /** * @note our "superclass" GSource takes care of removing * our pollFds record pointers from the source's and the * gmain context's pollFd lists when the GSource instance * is being detached from the gmain context, so we don't * need to do it ourselves. */ g_hash_table_destroy(source->descTable); } if (source->cachedReadyPollFdArray) { (void)g_array_free(source->cachedReadyPollFdArray, true/*free_segment*/); } }
/** ======================================================================== * ========================================================================= */ int psl_inet_make_sock_addr(int const reqFamily, const char* const addrStr, int const port, PslInetGenericSockAddr* const res, const char* const userLabel, const void* const cookie) { PSL_ASSERT(AF_INET == reqFamily || AF_INET6 == reqFamily || AF_UNSPEC == reqFamily); PSL_ASSERT(AF_UNSPEC != reqFamily || (addrStr && addrStr[0])); PSL_ASSERT(res); memset(res, 0, sizeof(*res)); res->family = AF_UNSPEC; PslInetIPAddress pslinaddr; int inaddrlen = 0; int actualFamily = reqFamily; if (AF_UNSPEC == reqFamily) { PSL_LOG_DEBUGLOW("%s (%s=%p): resoloving addrfamily (was AF_UNSPEC)", __func__, userLabel, cookie); PSL_ASSERT(addrStr); bool const gotAddr = psl_inet_ipaddr_from_string(addrStr, &pslinaddr, userLabel, cookie); if (gotAddr) { actualFamily = pslinaddr.family; inaddrlen = pslinaddr.len; } else { PSL_LOG_ERROR("%s (%s=%p): ERROR: could not determine " \ "address family from address string '%s'.", __func__, userLabel, cookie, PSL_LOG_OBFUSCATE_STR(addrStr)); return EINVAL; } } void* addrDst = NULL; if (AF_INET == actualFamily) { res->addrLength = sizeof(res->sa); res->sa.sin_family = actualFamily; res->sa.sin_port = htons(port); res->sa.sin_addr.s_addr = INADDR_ANY; addrDst = &res->sa.sin_addr; } else { PSL_ASSERT(AF_INET6 == actualFamily); res->addrLength = sizeof(res->sa6); res->sa6.sin6_family = actualFamily; //res->sa6.sin6_flowinfo = 0; ///< what should we do with these? //res->sa6.sin6_scope_id = 0; res->sa6.sin6_port = htons(port); res->sa6.sin6_addr = in6addr_any; addrDst = &res->sa6.sin6_addr; } if (addrStr && inaddrlen) { memcpy(addrDst, &pslinaddr.addr, inaddrlen); } else if (addrStr) { PSL_ASSERT(addrDst); int const ptonRes = inet_pton(actualFamily, addrStr, addrDst); if (ptonRes < 0) { /// unexpected address family - check errno int const saverrno = errno; PSL_LOG_ERROR("%s (%s=%p): ERROR: inet_pton() failed; " \ "family=%d, addrStr=%s, errno=%d (%s).", __func__, userLabel, cookie, actualFamily, PSL_LOG_OBFUSCATE_STR(addrStr), saverrno, strerror(saverrno)); PSL_ASSERT(saverrno); return saverrno; } else if (ptonRes == 0) { /// addr does not match family /// @note This case does not set errno PSL_LOG_ERROR("%s (%s=%p): ERROR: inet_pton() failed; Address " \ "does not match family; family=%d, addrStr=%s.", __func__, userLabel, cookie, actualFamily, PSL_LOG_OBFUSCATE_STR(addrStr)); return EINVAL; } } res->family = actualFamily; return 0; }// psl_inet_make_sock_addr