/* This file is part of TALER (C) 2016-2023 Taler Systems SA TALER 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 3, or (at your option) any later version. TALER 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 TALER; see the file COPYING. If not, see */ /** * @file bank-lib/fakebank_common_lp.c * @brief long-polling support for fakebank * @author Christian Grothoff */ #include "platform.h" #include #include #ifdef __linux__ #include #endif #include "taler_fakebank_lib.h" #include "taler_bank_service.h" #include "taler_mhd_lib.h" #include #include "fakebank.h" void TALER_FAKEBANK_lp_trigger_ (struct LongPoller *lp) { struct TALER_FAKEBANK_Handle *h = lp->h; struct Account *acc = lp->account; GNUNET_CONTAINER_DLL_remove (acc->lp_head, acc->lp_tail, lp); MHD_resume_connection (lp->conn); GNUNET_free (lp); h->mhd_again = true; #ifdef __linux__ if (-1 == h->lp_event) #else if ( (-1 == h->lp_event_in) && (-1 == h->lp_event_out) ) #endif { if (NULL != h->mhd_task) GNUNET_SCHEDULER_cancel (h->mhd_task); h->mhd_task = GNUNET_SCHEDULER_add_now (&TALER_FAKEBANK_run_mhd_, h); } } void * TALER_FAKEBANK_lp_expiration_thread_ (void *cls) { struct TALER_FAKEBANK_Handle *h = cls; GNUNET_assert (0 == pthread_mutex_lock (&h->big_lock)); while (! h->in_shutdown) { struct LongPoller *lp; int timeout_ms; lp = GNUNET_CONTAINER_heap_peek (h->lp_heap); while ( (NULL != lp) && GNUNET_TIME_absolute_is_past (lp->timeout)) { GNUNET_assert (lp == GNUNET_CONTAINER_heap_remove_root (h->lp_heap)); TALER_FAKEBANK_lp_trigger_ (lp); lp = GNUNET_CONTAINER_heap_peek (h->lp_heap); } if (NULL != lp) { struct GNUNET_TIME_Relative rem; unsigned long long left_ms; rem = GNUNET_TIME_absolute_get_remaining (lp->timeout); left_ms = rem.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; if (left_ms > INT_MAX) timeout_ms = INT_MAX; else timeout_ms = (int) left_ms; } else { timeout_ms = -1; /* infinity */ } GNUNET_assert (0 == pthread_mutex_unlock (&h->big_lock)); { struct pollfd p = { #ifdef __linux__ .fd = h->lp_event, #else .fd = h->lp_event_out, #endif .events = POLLIN }; int ret; ret = poll (&p, 1, timeout_ms); if (-1 == ret) { if (EINTR != errno) GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "poll"); } else if (1 == ret) { /* clear event */ uint64_t ev; ssize_t iret; #ifdef __linux__ iret = read (h->lp_event, &ev, sizeof (ev)); #else iret = read (h->lp_event_out, &ev, sizeof (ev)); #endif if (-1 == iret) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "read"); } else { GNUNET_break (sizeof (uint64_t) == iret); } } } GNUNET_assert (0 == pthread_mutex_lock (&h->big_lock)); } GNUNET_assert (0 == pthread_mutex_unlock (&h->big_lock)); return NULL; } /** * Trigger long pollers that might have been waiting * for @a t. * * @param h fakebank handle * @param t transaction to notify on */ void TALER_FAKEBANK_notify_transaction_ ( struct TALER_FAKEBANK_Handle *h, struct Transaction *t) { struct Account *debit_acc = t->debit_account; struct Account *credit_acc = t->credit_account; struct LongPoller *nxt; GNUNET_assert (0 == pthread_mutex_lock (&h->big_lock)); for (struct LongPoller *lp = debit_acc->lp_head; NULL != lp; lp = nxt) { nxt = lp->next; if (LP_DEBIT == lp->type) { GNUNET_assert (lp == GNUNET_CONTAINER_heap_remove_node (lp->hn)); TALER_FAKEBANK_lp_trigger_ (lp); } } for (struct LongPoller *lp = credit_acc->lp_head; NULL != lp; lp = nxt) { nxt = lp->next; if (LP_CREDIT == lp->type) { GNUNET_assert (lp == GNUNET_CONTAINER_heap_remove_node (lp->hn)); TALER_FAKEBANK_lp_trigger_ (lp); } } GNUNET_assert (0 == pthread_mutex_unlock (&h->big_lock)); } /** * Notify long pollers that a @a wo was updated. * Must be called with the "big_lock" still held. * * @param h fakebank handle * @param wo withdraw operation that finished */ void TALER_FAKEBANK_notify_withdrawal_ ( struct TALER_FAKEBANK_Handle *h, const struct WithdrawalOperation *wo) { struct Account *debit_acc = wo->debit_account; struct LongPoller *nxt; for (struct LongPoller *lp = debit_acc->lp_head; NULL != lp; lp = nxt) { nxt = lp->next; if ( (LP_WITHDRAW == lp->type) && (wo == lp->wo) ) { GNUNET_assert (lp == GNUNET_CONTAINER_heap_remove_node (lp->hn)); TALER_FAKEBANK_lp_trigger_ (lp); } } } /** * Task run when a long poller is about to time out. * Only used in single-threaded mode. * * @param cls a `struct TALER_FAKEBANK_Handle *` */ static void lp_timeout (void *cls) { struct TALER_FAKEBANK_Handle *h = cls; struct LongPoller *lp; h->lp_task = NULL; while (NULL != (lp = GNUNET_CONTAINER_heap_peek (h->lp_heap))) { if (GNUNET_TIME_absolute_is_future (lp->timeout)) break; GNUNET_assert (lp == GNUNET_CONTAINER_heap_remove_root (h->lp_heap)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Timeout reached for long poller %p\n", lp->conn); TALER_FAKEBANK_lp_trigger_ (lp); } if (NULL == lp) return; h->lp_task = GNUNET_SCHEDULER_add_at (lp->timeout, &lp_timeout, h); } /** * Reschedule the timeout task of @a h for time @a t. * * @param h fakebank handle * @param t when will the next connection timeout expire */ static void reschedule_lp_timeout (struct TALER_FAKEBANK_Handle *h, struct GNUNET_TIME_Absolute t) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Scheduling timeout task for %s\n", GNUNET_STRINGS_absolute_time_to_string (t)); #ifdef __linux__ if (-1 != h->lp_event) #else if (-1 != h->lp_event_in && -1 != h->lp_event_out) #endif { uint64_t num = 1; GNUNET_break (sizeof (num) == #ifdef __linux__ write (h->lp_event, &num, sizeof (num))); #else write (h->lp_event_in, &num, sizeof (num))); #endif } else { if (NULL != h->lp_task) GNUNET_SCHEDULER_cancel (h->lp_task); h->lp_task = GNUNET_SCHEDULER_add_at (t, &lp_timeout, h); } } void TALER_FAKEBANK_start_lp_ ( struct TALER_FAKEBANK_Handle *h, struct MHD_Connection *connection, struct Account *acc, struct GNUNET_TIME_Relative lp_timeout, enum LongPollType dir, const struct WithdrawalOperation *wo) { struct LongPoller *lp; bool toc; lp = GNUNET_new (struct LongPoller); lp->account = acc; lp->h = h; lp->wo = wo; lp->conn = connection; lp->timeout = GNUNET_TIME_relative_to_absolute (lp_timeout); lp->type = dir; lp->hn = GNUNET_CONTAINER_heap_insert (h->lp_heap, lp, lp->timeout.abs_value_us); toc = (lp == GNUNET_CONTAINER_heap_peek (h->lp_heap)); GNUNET_CONTAINER_DLL_insert (acc->lp_head, acc->lp_tail, lp); MHD_suspend_connection (connection); if (toc) reschedule_lp_timeout (h, lp->timeout); }