The `catch` statement runs a series of substatements in a block, but only if the previous xref:unlang/try.adoc[try] failed.
[ rcodes ]:: Zero or more module return codes. The return codes can be `disallow`, `fail`, `invalid`, or `reject`.
++
+If the `catch` statement is after a xref:unlang/timeout.adoc[timeout] statement, then the `catch` will run only if the `timeout` section fails. In this case, you must use `catch timeout { ... }`. The `catch timeout` syntax is not allowed in any other situation.
-[ statements ]:: The `unlang` commands which will be executed. A
-`catch` block can be empty.
+[ statements ]:: The `unlang` commands which will be executed. A `catch` block can be empty.
If no rcode is given the `catch` statement matches all of the codes listed above. Otherwise, the `catch` statement matches one of the listed rcodes.
}
----
-There is some overlap in functionality between `try` / xref:unlang/catch.adoc[catch] and xref:unlang/redundant.adoc[redundant]. The main difference is that a xref:unlang/catch.adoc[catch] statement can catch specific failure codes.
+There is some overlap in functionality between xref:unlang/try.adoc[try] / `catch` and xref:unlang/redundant.adoc[redundant]. The main difference is that a xref:unlang/catch.adoc[catch] statement can catch specific failure codes.
The xref:unlang/redundant.adoc[redundant] statement should be used to run
one of many similar modules. For example, the xref:unlang/redundant.adoc[redundant] statement could be used to choose one of four different `sql` modules in a fail-over fashion.
The time scale can be changed by appending `s`, `us`, `ms`, `ns`, etc. as
with all uses of `time_delta` values.
+As a special case, a `timeout` section can be immediately followed by
+a xref:unlang/catch.adoc[catch] statement, as `catch timeout { ... }`.
+In that case, the xref:unlang/catch.adoc[catch] section is run when
+the `timeout` expires.
+
.Example
[source,unlang]
----
}
----
-In general, the best way to use `timeout` is in conjunction with a
-`redundant` block. In the following example, the configuration allows
-the `proxy` module to run for `4` seconds. If the `proxy` module
-takes more than `4` seconds to return, _or_ if the `proxy` module
-fails, the `detail` module is called.
+== Timeout with catch
+
+In the following example, the configuration allows the `sql` module to
+run for `4` seconds. If the `sql` module takes more than `4` seconds
+to return, _or_ if the `sql` module fails, then the `detail` module is
+called.
+
+.Example using catch
+[source,unlang]
+----
+timeout 4s {
+ sql
+}
+catch timeout {
+ detail
+}
+----
+
+
+
+== Timeout with redundant
+
+The `timeout` can also be used inside of a
+xref:unlang/redundant.adoc[redundant] block. This example has
+_almost_ the same behavior as above. The difference here is that the
+`detail` file here is run on when _either_ the `sql` module fails, or
+the timeout is reached.
+
+Whether you choose to use a xref:unlang/redundant.adoc[redundant] or a
+xref:unlang/catch.adoc[catch] block after the `timeout` depends on
+your local requirements.
.Example using redundant
[source,unlang]
----
redundant
timeout 4s {
- proxy
+ sql
}
detail
}
----
-// Copyright (C) 2022 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Copyright (C) 2025 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
#include "unlang_priv.h"
#include "catch_priv.h"
-static unlang_action_t cleanup(unlang_stack_frame_t *frame, unlang_t *unlang)
-{
-
- /*
- * Clean up this frame now, so that stats, etc. will be
- * processed using the correct frame.
- */
- frame_cleanup(frame);
-
- /*
- * frame_next() will call cleanup *before* resetting the frame->instruction.
- * but since the instruction is NULL, no duplicate cleanups will happen.
- *
- * frame_next() will then set frame->instruction = frame->next, and everything will be OK.
- */
- frame->instruction = NULL;
- frame->next = unlang;
- return UNLANG_ACTION_EXECUTE_NEXT;
-}
-
static unlang_action_t catch_skip_to_next(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request, unlang_stack_frame_t *frame)
{
unlang_t *unlang;
break;
}
- return cleanup(frame, unlang);
+ return frame_set_next(frame, unlang);
}
static unlang_action_t unlang_catch(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
#ifndef NDEBUG
unlang_catch_t const *c = unlang_generic_to_catch(frame->instruction);
- fr_assert(c->catching[*p_result]);
+ fr_assert(c->timeout || c->catching[*p_result]);
#endif
/*
fr_assert(frame->instruction->type == UNLANG_TYPE_TRY);
+ /*
+ * 'try' at the end of a block without 'catch' should have been caught by the compiler.
+ */
+ fr_assert(frame->instruction->next);
+
for (unlang = frame->instruction->next;
unlang != NULL;
unlang = unlang->next) {
fr_assert(unlang != NULL);
- return cleanup(frame, unlang);
+ return frame_set_next(frame, unlang);
}
void unlang_catch_init(void)
typedef struct {
unlang_group_t group;
+ bool timeout; //!< are we catching a timeout
bool catching[RLM_MODULE_NUMCODES];
} unlang_catch_t;
unlang_catch_t *ca;
CONF_ITEM *prev;
char const *name;
+ bool catching_timeout = false;
static unlang_ext_t const ext = {
.type = UNLANG_TYPE_CATCH,
name = cf_section_name1(cf_item_to_section(prev));
fr_assert(name != NULL);
- /*
- * The previous thing has to be a section. And it has to
- * be either a "try" or a "catch".
- */
- if ((strcmp(name, "try") != 0) && (strcmp(name, "catch") != 0)) {
+ if (strcmp(name, "timeout") == 0) {
+ CONF_ITEM *next;
+
+ name = cf_section_name2(cs);
+ if (!name || (strcmp(name, "timeout") != 0)) {
+ cf_log_err(cs, "Invalid 'catch' after a 'timeout' section");
+ return NULL;
+ }
+
+ /*
+ * Check that the next section is NOT a "catch".
+ */
+ next = cf_item_next(cf_parent(ci), ci);
+ while (next && cf_item_is_data(next)) next = cf_item_next(cf_parent(ci), next);
+
+ if (next && cf_item_is_section(next) &&
+ (strcmp(cf_section_name1(cf_item_to_section(next)), "catch") == 0)) {
+ cf_log_err(next, "Cannot have two 'catch' statements after a 'timeout' section");
+ return NULL;
+ }
+
+ /*
+ * We comp
+ */
+ catching_timeout = true;
+
+ } else if ((strcmp(name, "try") != 0) && (strcmp(name, "catch") != 0)) {
+ /*
+ * The previous thing has to be a section. And it has to
+ * be either a "try" or a "catch".
+ */
goto fail;
}
ca = unlang_group_to_catch(g);
- /*
- * No arg2: catch all errors
- */
- if (!cf_section_name2(cs)) {
+ if (catching_timeout) {
+ ca->timeout = catching_timeout;
+
+ } else if (!cf_section_name2(cs)) {
+ /*
+ * No arg2: catch errors
+ */
ca->catching[RLM_MODULE_REJECT] = true;
ca->catching[RLM_MODULE_FAIL] = true;
ca->catching[RLM_MODULE_INVALID] = true;
#include <freeradius-devel/unlang/timeout.h>
#include "group_priv.h"
#include "timeout_priv.h"
-#include "interpret_priv.h"
+#include "unlang_priv.h"
typedef struct {
bool success;
static unlang_action_t unlang_timeout_resume_done(UNUSED rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
{
unlang_frame_state_timeout_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_timeout_t);
+ unlang_t *unlang;
- if (!state->success) {
- RWDEBUG("Timeout exceeded");
- return UNLANG_ACTION_FAIL;
+ unlang = frame->instruction->next;
+
+ /*
+ * No timeout, we go to the next instruction.
+ *
+ * Unless the next instruction is a "catch timeout", in which case we skip it.
+ */
+ if (state->success) {
+ if (!unlang || (unlang->type != UNLANG_TYPE_CATCH)) {
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ /*
+ * We skip the "catch" section if there's no timeout.
+ */
+ return frame_set_next(frame, unlang->next);
+ }
+
+ RWDEBUG("Timeout exceeded");
+
+ /*
+ * If there's a next instruction, AND it's a "catch", then we catch the timeout.
+ */
+ if (unlang && (unlang->type == UNLANG_TYPE_CATCH)) {
+ return frame_set_next(frame, unlang);
}
- return UNLANG_ACTION_CALCULATE_RESULT;
+ return UNLANG_ACTION_FAIL;
}
static unlang_action_t unlang_timeout_set(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
frame->process = process;
}
+static inline unlang_action_t frame_set_next(unlang_stack_frame_t *frame, unlang_t *unlang)
+{
+ /*
+ * Clean up this frame now, so that stats, etc. will be
+ * processed using the correct frame.
+ */
+ frame_cleanup(frame);
+
+ /*
+ * frame_next() will call cleanup *before* resetting the frame->instruction.
+ * but since the instruction is NULL, no duplicate cleanups will happen.
+ *
+ * frame_next() will then set frame->instruction = frame->next, and everything will be OK.
+ */
+ frame->instruction = NULL;
+ frame->next = unlang;
+ return UNLANG_ACTION_EXECUTE_NEXT;
+}
+
/** @name Conversion functions for converting #unlang_t to its specialisations
*
* Simple conversions: #unlang_module_t and #unlang_group_t are subclasses of #unlang_t,
--- /dev/null
+#
+# PRE: timeout
+#
+
+#
+# @todo - we have to add a leading '0' here, otherwise cf_file.c complains
+#
+timeout 0.1s {
+ delay_10s
+}
+catch timeout {
+ success
+}