diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index a4f6dad2..fc7e3579 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -131,6 +131,12 @@ lws_free_wsi(struct lws *wsi) lws_free(wsi); } +int +lws_should_be_on_timeout_list(struct lws *wsi) +{ + return wsi->timer_active || wsi->pending_timeout; +} + void lws_remove_from_timeout_list(struct lws *wsi) { @@ -149,9 +155,55 @@ lws_remove_from_timeout_list(struct lws *wsi) /* we're out of the list, we should not point anywhere any more */ wsi->timeout_list_prev = NULL; wsi->timeout_list = NULL; + lws_pt_unlock(pt); } +static void +lws_add_to_timeout_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + if (wsi->timeout_list_prev) + return; + + /* our next guy is current first guy */ + wsi->timeout_list = pt->timeout_list; + /* if there is a next guy, set his prev ptr to our next ptr */ + if (wsi->timeout_list) + wsi->timeout_list->timeout_list_prev = &wsi->timeout_list; + /* our prev ptr is first ptr */ + wsi->timeout_list_prev = &pt->timeout_list; + /* set the first guy to be us */ + *wsi->timeout_list_prev = wsi; +} + +LWS_VISIBLE void +lws_set_timer(struct lws *wsi, int secs) +{ + time_t now; + + if (secs < 0) { + wsi->timer_active = 0; + + if (!lws_should_be_on_timeout_list(wsi)) + lws_remove_from_timeout_list(wsi); + + return; + } + + time(&now); + + wsi->pending_timer_limit = secs; + wsi->pending_timer_set = now; + + if (!wsi->timer_active) { + wsi->timer_active = 1; + if (!wsi->pending_timeout) + lws_add_to_timeout_list(wsi); + } +} + LWS_VISIBLE void lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) { @@ -169,17 +221,8 @@ lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) time(&now); - if (reason && !wsi->timeout_list_prev) { - /* our next guy is current first guy */ - wsi->timeout_list = pt->timeout_list; - /* if there is a next guy, set his prev ptr to our next ptr */ - if (wsi->timeout_list) - wsi->timeout_list->timeout_list_prev = &wsi->timeout_list; - /* our prev ptr is first ptr */ - wsi->timeout_list_prev = &pt->timeout_list; - /* set the first guy to be us */ - *wsi->timeout_list_prev = wsi; - } + if (reason) + lws_add_to_timeout_list(wsi); lwsl_debug("%s: %p: %d secs\n", __func__, wsi, secs); wsi->pending_timeout_limit = secs; @@ -188,7 +231,7 @@ lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) lws_pt_unlock(pt); - if (!reason) + if (!reason && !lws_should_be_on_timeout_list(wsi)) lws_remove_from_timeout_list(wsi); } diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index b437b8df..f7975363 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -1354,6 +1354,12 @@ enum lws_callback_reasons { * protocol wants to take some action with this information. * \p in is the lws_vhost and \p len is the number of days left * before it expires, as a (ssize_t) */ + LWS_CALLBACK_TIMER = 73, + /**< When the time elapsed after a call to lws_set_timer(wsi, secs) + * is up, the wsi will get one of these callbacks. The deadline + * can be continuously extended into the future by later calls + * to lws_set_timer() before the deadline expires, or cancelled by + * lws_set_timer(wsi, -1); */ /****** add new things just above ---^ ******/ @@ -4404,6 +4410,22 @@ enum pending_timeout { */ LWS_VISIBLE LWS_EXTERN void lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); + +/** + * lws_set_timer() - schedules a callback on the wsi in the future + * + * \param wsi: Websocket connection instance + * \param secs: -1 removes any existing scheduled callback, otherwise the + * number of seconds in the future the callback will occur at. + * + * When the deadline expires, the wsi will get a callback of type + * LWS_CALLBACK_TIMER and the timer is exhausted. The deadline may be + * continuously deferred by further calls to lws_set_timer() with a later + * deadline, or cancelled by lws_set_timer(wsi, -1) + */ +LWS_VISIBLE LWS_EXTERN void +lws_set_timer(struct lws *wsi, int secs); + ///@} /*! \defgroup sending-data Sending data diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 9ce8a73d..e4764995 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1831,6 +1831,7 @@ struct lws { #endif const struct lws_protocols *protocol; struct lws **same_vh_protocol_prev, *same_vh_protocol_next; + /* we get on the list if either the timeout or the timer is valid */ struct lws *timeout_list; struct lws **timeout_list_prev; #if defined(LWS_WITH_PEER_LIMITS) @@ -1873,6 +1874,7 @@ struct lws { #endif #endif time_t pending_timeout_set; + time_t pending_timer_set; /* ints */ int position_in_fds_table; @@ -1915,6 +1917,8 @@ struct lws { unsigned int rxflow_will_be_applied:1; unsigned int event_pipe:1; + unsigned int timer_active:1; + #ifdef LWS_WITH_ACCESS_LOG unsigned int access_log_pending:1; #endif @@ -1944,6 +1948,7 @@ struct lws { unsigned short c_port; #endif unsigned short pending_timeout_limit; + unsigned short pending_timer_limit; uint8_t state; /* enum lws_connection_states */ uint8_t mode; /* enum connection_mode */ @@ -2002,6 +2007,9 @@ remove_wsi_socket_from_fds(struct lws *wsi); LWS_EXTERN int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len); +LWS_EXTERN int +lws_should_be_on_timeout_list(struct lws *wsi); + #ifndef LWS_LATENCY static inline void lws_latency(struct lws_context *context, struct lws *wsi, const char *action, diff --git a/lib/service.c b/lib/service.c index cd13aa12..5b9c4206 100644 --- a/lib/service.c +++ b/lib/service.c @@ -579,14 +579,36 @@ lws_service_timeout_check(struct lws *wsi, time_t sec) if (lws_ext_cb_active(wsi, LWS_EXT_CB_1HZ, NULL, sec) < 0) return 0; - if (!wsi->pending_timeout) - return 0; + /* + * is there a timer callback we should be doing? + */ + + if (wsi->timer_active && + lws_compare_time_t(wsi->context, sec, wsi->pending_timer_set) > + wsi->pending_timer_limit) { + wsi->timer_active = 0; + + if (wsi->protocol && + wsi->protocol->callback(wsi, LWS_CALLBACK_TIMER, + wsi->user_space, NULL, 0)) { + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + return 1; + } + + if (!lws_should_be_on_timeout_list(wsi)) { + lws_remove_from_timeout_list(wsi); + + return 0; + } + } /* * if we went beyond the allowed time, kill the * connection */ - if (lws_compare_time_t(wsi->context, sec, wsi->pending_timeout_set) > + if (wsi->pending_timeout && + lws_compare_time_t(wsi->context, sec, wsi->pending_timeout_set) > wsi->pending_timeout_limit) { if (wsi->desc.sockfd != LWS_SOCK_INVALID && diff --git a/plugins/protocol_dumb_increment.c b/plugins/protocol_dumb_increment.c index c5467b6d..258ac2b9 100644 --- a/plugins/protocol_dumb_increment.c +++ b/plugins/protocol_dumb_increment.c @@ -91,6 +91,8 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_ESTABLISHED: pss->number = 0; + /* just to test the timer api */ + // lws_set_timer(wsi, 3); break; case LWS_CALLBACK_SERVER_WRITEABLE: @@ -115,6 +117,11 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, } break; + case LWS_CALLBACK_TIMER: + lwsl_notice("%s: LWS_CALLBACK_TIMER\n", __func__); + lws_set_timer(wsi, 3); + break; + default: break; } diff --git a/test-apps/test-server-dumb-increment.c b/test-apps/test-server-dumb-increment.c index dc2548b5..5b58316c 100644 --- a/test-apps/test-server-dumb-increment.c +++ b/test-apps/test-server-dumb-increment.c @@ -35,6 +35,8 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, case LWS_CALLBACK_ESTABLISHED: pss->number = 0; + /* just to test the timer api */ + lws_set_timer(wsi, 3); break; case LWS_CALLBACK_SERVER_WRITEABLE: @@ -62,6 +64,12 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason, return -1; } break; + + case LWS_CALLBACK_TIMER: + lwsl_notice("%s: LWS_CALLBACK_TIMER\n", __func__); + lws_set_timer(wsi, 3); + break; + /* * this just demonstrates how to use the protocol filter. If you won't * study and reject connections based on header content, you don't need