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