Willy Tarreau | 8f7133e | 2022-05-06 18:00:24 +0200 | [diff] [blame] | 1 | Instantiation of applet contexts (appctx) in 2.6. |
| 2 | |
| 3 | |
| 4 | 1. Background |
| 5 | |
| 6 | Most applets are in fact simplified services that are called by the CLI when a |
| 7 | registered keyword is matched. Some of them only have a ->parse() function |
| 8 | which immediately returns with a final result, while others will return zero |
| 9 | asking for the->io_handler() one to be called till the end. For these ones, a |
| 10 | context is generally needed between calls to know where to restart from. |
| 11 | |
| 12 | Other applets are completely autonomous applets with their init function and |
| 13 | an I/O handler, and these ones also need a persistent context between calls to |
| 14 | the I/O handler. These ones are typically instantiated by "use-service" or by |
| 15 | other means. |
| 16 | |
| 17 | Originally a few integers were provided to keep a trivial state (st0, st1, st2) |
| 18 | and these ones progressively proved insufficient, leading to a "ctx.cli" sub- |
| 19 | context that was allowed to use extra fields of various types. Other applets |
| 20 | preferred to use their own context definition. |
| 21 | |
| 22 | All this resulted in the appctx->ctx to contain a myriad of definitions of |
Ilya Shipitsin | 3b64a28 | 2022-07-29 22:26:53 +0500 | [diff] [blame] | 23 | various service contexts, and in some services abusing other services' |
Willy Tarreau | 8f7133e | 2022-05-06 18:00:24 +0200 | [diff] [blame] | 24 | definitions by laziness, and others being extended to use their own definition |
| 25 | after having run for a long time on the generic types, some of which were not |
| 26 | noticed and mistakenly used the same storage locations by accident. A massive |
| 27 | cleanup was needed. |
| 28 | |
| 29 | |
| 30 | 2. New approach in 2.6 |
| 31 | |
| 32 | In 2.6, there's an "svcctx" pointer that's initialized to NULL before any |
| 33 | instantiation of an applet or of a CLI keyword's function. Applets and keyword |
| 34 | handlers are free to make it point wherever they want, and to find it unaltered |
| 35 | between subsequent calls, including up to the ->release() call. The "st2" state |
| 36 | that was totally abused with random enums is not used anymore and was marked as |
| 37 | deprecated. It's still initialized to zero before the first call though. |
| 38 | |
| 39 | One special area, "svc.storage[]", is large enough to contain any of the |
| 40 | contexts that used to be present under "appctx->ctx". The "svcctx" may be set |
| 41 | to point to this area so that a small structure can be allocated for free and |
| 42 | without requiring error checking. In order to make this easier, a specially |
| 43 | purposed function is provided: "applet_reserve_svcctx()". This function will |
| 44 | require the caller to indicate how large an area it needs, and will return a |
| 45 | pointer to this area after checking that it fits. If it does not, haproxy will |
| 46 | crash. This is purposely done so that it's known during development that if a |
Ilya Shipitsin | 3b64a28 | 2022-07-29 22:26:53 +0500 | [diff] [blame] | 47 | small structure doesn't fit, a different approach is required. |
Willy Tarreau | 8f7133e | 2022-05-06 18:00:24 +0200 | [diff] [blame] | 48 | |
| 49 | As such, for the vast majority of commands, the process is the following one: |
| 50 | |
| 51 | struct foo_ctx { |
| 52 | int myfield1; |
| 53 | int myfield2; |
| 54 | char *myfield3; |
| 55 | }; |
| 56 | |
| 57 | int io_handler(struct appctx *appctx) |
| 58 | { |
| 59 | struct foo_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); |
| 60 | |
| 61 | if (!ctx->myfield1) { |
| 62 | /* first call */ |
| 63 | ctx->myfield1++; |
| 64 | } |
| 65 | ... |
| 66 | } |
| 67 | |
| 68 | The pointer may be directly accessed from the I/O handler if it's known that it |
| 69 | was already reserved by the init handler or parsing function. Otherwise it's |
| 70 | guaranteed to be NULL so that can also serve as a test for a first call: |
| 71 | |
| 72 | int parse_handler(struct appctx *appctx) |
| 73 | { |
| 74 | struct foo_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx)); |
| 75 | ctx->myfield1 = 12; |
| 76 | return 0; |
| 77 | } |
| 78 | |
| 79 | int io_handler(struct appctx *appctx) |
| 80 | { |
| 81 | struct foo_ctx *ctx = appctx->svcctx; |
| 82 | |
| 83 | for (; !ctx->myfield1; ctx->myfield1--) { |
| 84 | do_something(); |
| 85 | } |
| 86 | ... |
| 87 | } |
| 88 | |
| 89 | There is no need to free anything because that space is not allocated but just |
| 90 | points to a reserved area. |
| 91 | |
| 92 | If it is too small (its size is APPLET_MAX_SVCCTX bytes), it is preferable to |
| 93 | use it with dynamically allocated structures (pools, malloc, etc). For example: |
| 94 | |
| 95 | int io_handler(struct appctx *appctx) |
| 96 | { |
| 97 | struct foo_ctx *ctx = appctx->svcctx; |
| 98 | |
| 99 | if (!ctx) { |
| 100 | /* first call */ |
| 101 | ctx = pool_alloc(pool_foo_ctx); |
| 102 | if (!ctx) |
| 103 | return 1; |
| 104 | } |
| 105 | ... |
| 106 | } |
| 107 | |
| 108 | void io_release(struct appctx *appctx) |
| 109 | { |
| 110 | pool_free(pool_foo_ctx, appctx->svcctx); |
| 111 | } |
| 112 | |
| 113 | The CLI code itself uses this mechanism for the cli_print_*() functions. Since |
| 114 | these functions are terminal (i.e. not meant to be used in the middle of an I/O |
| 115 | handler as they share the same contextual space), they always reset the svcctx |
| 116 | pointer to place it to the "cli_print_ctx" mapped in ->svc.storage. |
| 117 | |
| 118 | |
| 119 | 3. Transition for old code |
| 120 | |
| 121 | A lot of care was taken to make the transition as smooth as possible for |
| 122 | out-of-tree code since that's an API change. A dummy "ctx.cli" struct still |
| 123 | exists in the appctx struct, and it happens to map perfectly to the one set by |
| 124 | cli_print_*, so that if some code uses a mix of both, it will still work. |
| 125 | However, it will build with "deprecated" warnings allowing to spot the |
| 126 | remaining places. It's a good exercise to rename "ctx.cli" in "appctx" and see |
| 127 | if the code still compiles. |
| 128 | |
| 129 | Regarding the "st2" sub-state, it will disappear as well after 2.6, but is |
| 130 | still provided and initialized so that code relying on it will still work even |
| 131 | if it builds with deprecation warnings. The correct approach is to move this |
| 132 | state into the newly defined applet's context, and to stop using the stats |
| 133 | enums STAT_ST_* that often barely match the needs and result in code that is |
| 134 | more complicated than desired (the STAT_ST_* enum values have also been marked |
| 135 | as deprecated). |
| 136 | |
| 137 | The code dealing with "show fd", "show sess" and the peers applet show good |
| 138 | examples of how to convert a registered keyword or an applet. |
| 139 | |
| 140 | All this transition code requires complex layouts that will be removed during |
| 141 | 2.7-dev so there is no other long-term option but to update the code (or better |
| 142 | get it merged if it can be useful to other users). |