1.. Licensed to the Apache Software Foundation (ASF) under one 2 or more contributor license agreements. See the NOTICE file 3 distributed with this work for additional information 4 regarding copyright ownership. The ASF licenses this file 5 to you under the Apache License, Version 2.0 (the 6 "License"); you may not use this file except in compliance 7 with the License. You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11 Unless required by applicable law or agreed to in writing, 12 software distributed under the License is distributed on an 13 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 KIND, either express or implied. See the License for the 15 specific language governing permissions and limitations 16 under the License. 17 18.. include:: ../../common.defs 19 20.. _developer-plugins-mutexes: 21 22Mutexes 23******* 24 25.. toctree:: 26 :maxdepth: 1 27 28A *mutex* is the basic synchronization method used within Traffic 29Server to protect data from simultaneous access by multiple threads. A 30mutex acts as a lock that protects data in one program thread from being 31accessed by another thread. 32 33The Traffic Server API provides two functions that attempt to access and 34lock the data: :c:func:`TSMutexLockTry` and :c:func:`TSMutexLock`. 35``TSMutexLock`` is a blocking call - if you use it, you can slow 36Traffic Server performance because transaction processing pauses until 37the mutex is unlocked. It should be used only on threads created by the 38plugin ``TSContThreadCreate``. Never use it on a continuation handler 39called back by the Cache, Net, or Event Processor. Even if the critical 40section is very small, do not use it. If you need to update a flag, then 41set a variable and/or use atomic operations. If :c:func:`TSMutexLock` is used 42in any case other than the one recommended above, then the result will 43be a serious performance impact. 44 45``TSMutexLockTry``, on the other hand, attempts to lock the mutex 46only if it is unlocked (i.e., not being used by another thread). It 47should be used in all cases other than the above mentioned 48``TSMutexLock`` case. If the ``TSMutexLockTry`` attempt fails, then you 49can schedule a future attempt (which must be at least 10 milliseconds 50later). 51 52In general, you should use ``TSMutexLockTry`` instead of 53``TSMutexLock``. 54 55- ``TSMutexLockTry`` is required if you are tying to lock Traffic 56 Server internal or system resources (such as the network, cache, 57 event processor, HTTP state machines, and IO buffers). 58 59- ``TSMutexLockTry`` is required if you are making any blocking calls 60 (such as network, cache, or file IO calls). 61 62- ``TSMutexLock`` might not be necessary if you are not making 63 blocking calls and if you are only accessing local resources. 64 65The Traffic Server API uses the ``TSMutex`` type for a mutex. There are 66two typical uses of mutex. One use is for locking global data or data 67shared by various continuations. The other typical usage is for locking 68data associated with a continuation (i.e., data that might be accessed 69by other continuations). 70 71Locking Global Data 72=================== 73 74The :ref:`denylist-1.c` sample plugin implements a mutex that locks global 75data. The denylist plugin reads sites to be denied from a 76configuration file; file read operations are protected by a mutex 77created in :c:func:`TSPluginInit`. The :ref:`denylist-1.c` code uses 78:c:func:`TSMutexLockTry` instead of :c:func:`TSMutexLock`. For more detailed 79information, see the :ref:`denylist-1.c` code; 80start by looking at the :c:func:`TSPluginInit` function. 81 82General guidelines for locking shared data are as follows: 83 841. Create a mutex for the shared data with 85 :c:func:`TSMutexCreate`. 86 872. Whenever you need to read or modify this data, first lock it by 88 calling 89 :c:func:`TSMutexLockTry`; 90 then read or modify the data. 91 923. When you are done with the data, unlock it with 93 :c:func:`TSMutexUnlock`. 94 If you are unlocking data accessed during the processing of an HTTP 95 transaction, then you must unlock it before calling 96 :c:func:`TSHttpTxnReenable`. 97 98Protecting a Continuation's Data 99================================ 100 101You must create a mutex to protect a continuation's data if it might be 102accessed by other continuations or processes. Here's how: 103 1041. | Create a mutex for the continuation using ``TSMutexCreate``. 105 | For example: 106 107 .. code-block:: c 108 109 TSMutex mutexp; 110 mutexp = TSMutexCreate (); 111 1122. | When you create the continuation, specify this mutex as the 113 continuation's mutex. 114 | For example: 115 116 .. code-block:: c 117 118 TSCont contp; 119 contp = TSContCreate (handler, mutexp); 120 121If any other functions want to access ``contp``'s data, then it is up to 122them to get ``contp``'s mutex (using, for example, ``TSContMutexGet``) 123to lock it. For usage, see the sample Protocol plugin. 124 125How to Associate a Continuation With Every HTTP Transaction 126=========================================================== 127 128There could be several reasons why you'd want to create a continuation 129for each HTTP transaction that calls back your plugin. 130 131Some potential scenarios are listed below. 132 133- You want to register hooks locally with the new continuation instead 134 of registering them globally to the continuation plugin. 135 136- You want to store data specific to each HTTP transaction that you 137 might need to reuse across various hooks. 138 139- You're using APIs (like ``TSHostLookup``) that will call back the 140 continuation with a certain event. 141 142How to Add the New Continuation 143=============================== 144 145A typical way of adding the new continuation is by registering the 146plugin continuation to be called back by HTTP transactions globally when 147they reach ``TS_HTTP_TXN_START_HOOK``. Refer to the example below, which 148uses a transaction-specific continuation called ``txn_contp``. 149 150.. code-block:: c 151 152 void TSPluginInit(int argc, const char *argv[]) 153 { 154 /* Plugin continuation */ 155 TSCont contp; 156 if ((contp = TSContCreate (plugin_cont_handler, NULL)) == TS_ERROR_PTR) { 157 LOG_ERROR("TSContCreate"); 158 } else { 159 if (TSHttpHookAdd (TS_HTTP_TXN_START_HOOK, contp) == TS_ERROR) { 160 LOG_ERROR("TSHttpHookAdd"); 161 } 162 } 163 } 164 165In the plugin continuation handler, create the new continuation 166``txn_contp`` and then register it to be called back at 167``TS_HTTP_TXN_CLOSE_HOOK``: 168 169.. code-block:: c 170 171 static int plugin_cont_handler(TSCont contp, TSEvent event, void *edata) 172 { 173 TSHttpTxn txnp = (TSHttpTxn)edata; 174 TSCont txn_contp; 175 176 switch (event) { 177 case TS_EVENT_HTTP_TXN_START: 178 /* Create the HTTP txn continuation */ 179 txn_contp = TSContCreate(txn_cont_handler, NULL); 180 181 /* Register txn_contp to be called back when txnp reaches TS_HTTP_TXN_CLOSE_HOOK */ 182 if (TSHttpTxnHookAdd (txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp) == TS_ERROR) { 183 LOG_ERROR("TSHttpTxnHookAdd"); 184 } 185 186 break; 187 188 default: 189 TSAssert(!"Unexpected Event"); 190 break; 191 } 192 193 if (TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE) == TS_ERROR) { 194 LOG_ERROR("TSHttpTxnReenable"); 195 } 196 197 return 0; 198 } 199 200Remember that the ``txn_contp`` handler must destroy itself when the 201HTTP transaction is closed. If you forget to do this, then your plugin 202will have a memory leak. 203 204.. code-block:: c 205 206 207 static int txn_cont_handler(TSCont txn_contp, TSEvent event, void *edata) 208 { 209 TSHttpTxn txnp; 210 switch (event) { 211 case TS_EVENT_HTTP_TXN_CLOSE: 212 txnp = (TSHttpTxn) edata; 213 TSContDestroy(txn_contp); 214 break; 215 216 default: 217 TSAssert(!"Unexpected Event"); 218 break; 219 } 220 221 if (TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE) == TS_ERROR) { 222 LOG_ERROR("TSHttpTxnReenable"); 223 } 224 225 return 0; 226 } 227 228How to Store Data Specific to Each HTTP Transaction 229=================================================== 230 231For the example above, store the data in the ``txn_contp`` data 232structure - this means that you'll create your own data structure. Now 233suppose you want to store the state of the HTTP transaction: 234 235.. code-block:: c 236 237 typedef struct { 238 int state; 239 } ContData; 240 241You need to allocate the memory and initialize this structure for each 242HTTP ``txnp``. You can do that in the plugin continuation handler when 243it is called back with ``TS_EVENT_HTTP_TXN_START`` 244 245.. code-block:: c 246 247 static int plugin_cont_handler(TSCont contp, TSEvent event, void *edata) 248 { 249 TSHttpTxn txnp = (TSHttpTxn)edata; 250 TSCont txn_contp; 251 ContData *contData; 252 253 switch (event) { 254 case TS_EVENT_HTTP_TXN_START: 255 /* Create the HTTP txn continuation */ 256 txn_contp = TSContCreate(txn_cont_handler, NULL); 257 258 /* Allocate and initialize the txn_contp data */ 259 contData = (ContData*) TSmalloc(sizeof(ContData)); 260 contData->state = 0; 261 if (TSContDataSet(txn_contp, contData) == TS_ERROR) { 262 LOG_ERROR("TSContDataSet"); 263 } 264 265 /* Register txn_contp to be called back when txnp reaches TS_HTTP_TXN_CLOSE_HOOK */ 266 if (TSHttpTxnHookAdd (txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp) == TS_ERROR) { 267 LOG_ERROR("TSHttpTxnHookAdd"); 268 } 269 270 break; 271 272 default: 273 TSAssert(!"Unexpected Event"); 274 break; 275 } 276 277 if (TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE) == TS_ERROR) { 278 LOG_ERROR("TSHttpTxnReenable"); 279 } 280 281 return 0; 282 } 283 284For accessing this data from anywhere, use TSContDataGet: 285 286.. code-block:: c 287 288 TSCont txn_contp; 289 ContData *contData; 290 291 contData = TSContDataGet(txn_contp); 292 if (contData == TS_ERROR_PTR) { 293 LOG_ERROR("TSContDataGet"); 294 } 295 contData->state = 1; 296 297Remember to free this memory before destroying the continuation: 298 299.. code-block:: c 300 301 static int txn_cont_handler(TSCont txn_contp, TSEvent event, void *edata) 302 { 303 TSHttpTxn txnp; 304 ContData *contData; 305 switch (event) { 306 case TS_EVENT_HTTP_TXN_CLOSE: 307 txnp = (TSHttpTxn) edata; 308 contData = TSContDataGet(txn_contp); 309 if (contData == TS_ERROR_PTR) { 310 LOG_ERROR("TSContDataGet"); 311 } else { 312 TSfree(contData); 313 } 314 TSContDestroy(txn_contp); 315 break; 316 317 default: 318 TSAssert(!"Unexpected Event"); 319 break; 320 } 321 322 if (TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE) == TS_ERROR) { 323 LOG_ERROR("TSHttpTxnReenable"); 324 } 325 326 return 0; 327 } 328 329Using Locks 330=========== 331 332You do not need to use locks when a continuation has registered itself 333to be called back by HTTP hooks and it only uses the HTTP APIs. In the 334example above, the continuation ``txn_contp`` has registered itself to 335be called back at HTTP hooks and it only uses the HTTP APIs. In this 336case only, it's safe to access data shared between ``txnp`` and 337``txn_contp`` without grabbing a lock. In the example above, 338``txn_contp`` is created with a ``NULL`` mutex. This works because the 339HTTP transaction ``txnp`` is the only one that will call back 340``txn_contp``, and you are guaranteed that ``txn_contp`` will be called 341back only one hook at a time. After processing is finished, 342``txn_contp`` will re-enable ``txnp``. 343 344In all other cases, you should create a mutex with the continuation. In 345general, a lock is needed when you're using iocore APIs or any other API 346where ``txn_contp`` is scheduled to be called back by a processor (such 347as the cache processor, the DNS processor, etc.). This ensures that 348``txn_contp`` is called back sequentially and not simultaneously. In 349other words, you need to ensure that ``txn_contp`` will not be called 350back by both ``txnp`` and the cache processor at the same time, since 351this will result in a situation wherein you're executing two pieces of 352code in conflict. 353 354Special Case: Continuations Created for HTTP Transactions 355========================================================= 356 357If your plugin creates a new continuation for each HTTP transaction, 358then you probably don't need to create a new mutex for it because each 359HTTP transaction (``TSHttpTxn`` object) already has its own mutex. 360 361In the example below, it's not necessary to specify a mutex for the 362continuation created in ``txn_handler``: 363 364.. code-block:: c 365 366 static void 367 txn_handler (TSHttpTxn txnp, TSCont contp) { 368 TSCont newCont; 369 .... 370 newCont = TSContCreate (newCont_handler, NULL); 371 // It's not necessary to create a new mutex for newCont. 372 373 ... 374 375 TSHttpTxnReenable (txnp, TS_EVENT_HTTP_CONTINUE); 376 } 377 378 static int 379 test_plugin (TSCont contp, TSEvent event, void *edata) { 380 TSHttpTxn txnp = (TSHttpTxn) edata; 381 382 switch (event) { 383 case TS_EVENT_HTTP_READ_REQUEST_HDR: 384 txn_handler (txnp, contp); 385 return 0; 386 default: 387 break; 388 } 389 return 0; 390 } 391 392The mutex functions are listed below: 393 394- :c:func:`TSMutexCreate` 395- :c:func:`TSMutexLock` 396- :c:func:`TSMutexLockTry` 397 398