blob: ae59e41ea98c7ad71dec32806dbcd812cf2eac84 [file] [log] [blame]
.. Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
.. include:: ../../common.defs
.. _developer-plugins-mutexes:
Mutexes
*******
.. toctree::
:maxdepth: 1
A *mutex* is the basic synchronization method used within Traffic
Server to protect data from simultaneous access by multiple threads. A
mutex acts as a lock that protects data in one program thread from being
accessed by another thread.
The Traffic Server API provides two functions that attempt to access and
lock the data: :c:func:`TSMutexLockTry` and :c:func:`TSMutexLock`.
``TSMutexLock`` is a blocking call - if you use it, you can slow
Traffic Server performance because transaction processing pauses until
the mutex is unlocked. It should be used only on threads created by the
plugin ``TSContThreadCreate``. Never use it on a continuation handler
called back by the Cache, Net, or Event Processor. Even if the critical
section is very small, do not use it. If you need to update a flag, then
set a variable and/or use atomic operations. If :c:func:`TSMutexLock` is used
in any case other than the one recommended above, then the result will
be a serious performance impact.
``TSMutexLockTry``, on the other hand, attempts to lock the mutex
only if it is unlocked (i.e., not being used by another thread). It
should be used in all cases other than the above mentioned
``TSMutexLock`` case. If the ``TSMutexLockTry`` attempt fails, then you
can schedule a future attempt (which must be at least 10 milliseconds
later).
In general, you should use ``TSMutexLockTry`` instead of
``TSMutexLock``.
- ``TSMutexLockTry`` is required if you are tying to lock Traffic
Server internal or system resources (such as the network, cache,
event processor, HTTP state machines, and IO buffers).
- ``TSMutexLockTry`` is required if you are making any blocking calls
(such as network, cache, or file IO calls).
- ``TSMutexLock`` might not be necessary if you are not making
blocking calls and if you are only accessing local resources.
The Traffic Server API uses the ``TSMutex`` type for a mutex. There are
two typical uses of mutex. One use is for locking global data or data
shared by various continuations. The other typical usage is for locking
data associated with a continuation (i.e., data that might be accessed
by other continuations).
Locking Global Data
===================
The :ref:`denylist-1.c` sample plugin implements a mutex that locks global
data. The denylist plugin reads sites to be denied from a
configuration file; file read operations are protected by a mutex
created in :c:func:`TSPluginInit`. The :ref:`denylist-1.c` code uses
:c:func:`TSMutexLockTry` instead of :c:func:`TSMutexLock`. For more detailed
information, see the :ref:`denylist-1.c` code;
start by looking at the :c:func:`TSPluginInit` function.
General guidelines for locking shared data are as follows:
1. Create a mutex for the shared data with
:c:func:`TSMutexCreate`.
2. Whenever you need to read or modify this data, first lock it by
calling
:c:func:`TSMutexLockTry`;
then read or modify the data.
3. When you are done with the data, unlock it with
:c:func:`TSMutexUnlock`.
If you are unlocking data accessed during the processing of an HTTP
transaction, then you must unlock it before calling
:c:func:`TSHttpTxnReenable`.
Protecting a Continuation's Data
================================
You must create a mutex to protect a continuation's data if it might be
accessed by other continuations or processes. Here's how:
1. | Create a mutex for the continuation using ``TSMutexCreate``.
| For example:
.. code-block:: c
TSMutex mutexp;
mutexp = TSMutexCreate ();
2. | When you create the continuation, specify this mutex as the
continuation's mutex.
| For example:
.. code-block:: c
TSCont contp;
contp = TSContCreate (handler, mutexp);
If any other functions want to access ``contp``'s data, then it is up to
them to get ``contp``'s mutex (using, for example, ``TSContMutexGet``)
to lock it. For usage, see the sample Protocol plugin.
How to Associate a Continuation With Every HTTP Transaction
===========================================================
There could be several reasons why you'd want to create a continuation
for each HTTP transaction that calls back your plugin.
Some potential scenarios are listed below.
- You want to register hooks locally with the new continuation instead
of registering them globally to the continuation plugin.
- You want to store data specific to each HTTP transaction that you
might need to reuse across various hooks.
- You're using APIs (like ``TSHostLookup``) that will call back the
continuation with a certain event.
How to Add the New Continuation
===============================
A typical way of adding the new continuation is by registering the
plugin continuation to be called back by HTTP transactions globally when
they reach ``TS_HTTP_TXN_START_HOOK``. Refer to the example below, which
uses a transaction-specific continuation called ``txn_contp``.
.. code-block:: c
void TSPluginInit(int argc, const char *argv[])
{
/* Plugin continuation */
TSCont contp;
if ((contp = TSContCreate (plugin_cont_handler, NULL)) == TS_ERROR_PTR) {
LOG_ERROR("TSContCreate");
} else {
if (TSHttpHookAdd (TS_HTTP_TXN_START_HOOK, contp) == TS_ERROR) {
LOG_ERROR("TSHttpHookAdd");
}
}
}
In the plugin continuation handler, create the new continuation
``txn_contp`` and then register it to be called back at
``TS_HTTP_TXN_CLOSE_HOOK``:
.. code-block:: c
static int plugin_cont_handler(TSCont contp, TSEvent event, void *edata)
{
TSHttpTxn txnp = (TSHttpTxn)edata;
TSCont txn_contp;
switch (event) {
case TS_EVENT_HTTP_TXN_START:
/* Create the HTTP txn continuation */
txn_contp = TSContCreate(txn_cont_handler, NULL);
/* Register txn_contp to be called back when txnp reaches TS_HTTP_TXN_CLOSE_HOOK */
if (TSHttpTxnHookAdd (txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp) == TS_ERROR) {
LOG_ERROR("TSHttpTxnHookAdd");
}
break;
default:
TSAssert(!"Unexpected Event");
break;
}
if (TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE) == TS_ERROR) {
LOG_ERROR("TSHttpTxnReenable");
}
return 0;
}
Remember that the ``txn_contp`` handler must destroy itself when the
HTTP transaction is closed. If you forget to do this, then your plugin
will have a memory leak.
.. code-block:: c
static int txn_cont_handler(TSCont txn_contp, TSEvent event, void *edata)
{
TSHttpTxn txnp;
switch (event) {
case TS_EVENT_HTTP_TXN_CLOSE:
txnp = (TSHttpTxn) edata;
TSContDestroy(txn_contp);
break;
default:
TSAssert(!"Unexpected Event");
break;
}
if (TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE) == TS_ERROR) {
LOG_ERROR("TSHttpTxnReenable");
}
return 0;
}
How to Store Data Specific to Each HTTP Transaction
===================================================
For the example above, store the data in the ``txn_contp`` data
structure - this means that you'll create your own data structure. Now
suppose you want to store the state of the HTTP transaction:
.. code-block:: c
typedef struct {
int state;
} ContData;
You need to allocate the memory and initialize this structure for each
HTTP ``txnp``. You can do that in the plugin continuation handler when
it is called back with ``TS_EVENT_HTTP_TXN_START``
.. code-block:: c
static int plugin_cont_handler(TSCont contp, TSEvent event, void *edata)
{
TSHttpTxn txnp = (TSHttpTxn)edata;
TSCont txn_contp;
ContData *contData;
switch (event) {
case TS_EVENT_HTTP_TXN_START:
/* Create the HTTP txn continuation */
txn_contp = TSContCreate(txn_cont_handler, NULL);
/* Allocate and initialize the txn_contp data */
contData = (ContData*) TSmalloc(sizeof(ContData));
contData->state = 0;
if (TSContDataSet(txn_contp, contData) == TS_ERROR) {
LOG_ERROR("TSContDataSet");
}
/* Register txn_contp to be called back when txnp reaches TS_HTTP_TXN_CLOSE_HOOK */
if (TSHttpTxnHookAdd (txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp) == TS_ERROR) {
LOG_ERROR("TSHttpTxnHookAdd");
}
break;
default:
TSAssert(!"Unexpected Event");
break;
}
if (TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE) == TS_ERROR) {
LOG_ERROR("TSHttpTxnReenable");
}
return 0;
}
For accessing this data from anywhere, use TSContDataGet:
.. code-block:: c
TSCont txn_contp;
ContData *contData;
contData = TSContDataGet(txn_contp);
if (contData == TS_ERROR_PTR) {
LOG_ERROR("TSContDataGet");
}
contData->state = 1;
Remember to free this memory before destroying the continuation:
.. code-block:: c
static int txn_cont_handler(TSCont txn_contp, TSEvent event, void *edata)
{
TSHttpTxn txnp;
ContData *contData;
switch (event) {
case TS_EVENT_HTTP_TXN_CLOSE:
txnp = (TSHttpTxn) edata;
contData = TSContDataGet(txn_contp);
if (contData == TS_ERROR_PTR) {
LOG_ERROR("TSContDataGet");
} else {
TSfree(contData);
}
TSContDestroy(txn_contp);
break;
default:
TSAssert(!"Unexpected Event");
break;
}
if (TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE) == TS_ERROR) {
LOG_ERROR("TSHttpTxnReenable");
}
return 0;
}
Using Locks
===========
You do not need to use locks when a continuation has registered itself
to be called back by HTTP hooks and it only uses the HTTP APIs. In the
example above, the continuation ``txn_contp`` has registered itself to
be called back at HTTP hooks and it only uses the HTTP APIs. In this
case only, it's safe to access data shared between ``txnp`` and
``txn_contp`` without grabbing a lock. In the example above,
``txn_contp`` is created with a ``NULL`` mutex. This works because the
HTTP transaction ``txnp`` is the only one that will call back
``txn_contp``, and you are guaranteed that ``txn_contp`` will be called
back only one hook at a time. After processing is finished,
``txn_contp`` will reenable ``txnp``.
In all other cases, you should create a mutex with the continuation. In
general, a lock is needed when you're using iocore APIs or any other API
where ``txn_contp`` is scheduled to be called back by a processor (such
as the cache processor, the DNS processor, etc.). This ensures that
``txn_contp`` is called back sequentially and not simultaneously. In
other words, you need to ensure that ``txn_contp`` will not be called
back by both ``txnp`` and the cache processor at the same time, since
this will result in a situation wherein you're executing two pieces of
code in conflict.
Special Case: Continuations Created for HTTP Transactions
=========================================================
If your plugin creates a new continuation for each HTTP transaction,
then you probably don't need to create a new mutex for it because each
HTTP transaction (``TSHttpTxn`` object) already has its own mutex.
In the example below, it's not necessary to specify a mutex for the
continuation created in ``txn_handler``:
.. code-block:: c
static void
txn_handler (TSHttpTxn txnp, TSCont contp) {
TSCont newCont;
....
newCont = TSContCreate (newCont_handler, NULL);
// It's not necessary to create a new mutex for newCont.
...
TSHttpTxnReenable (txnp, TS_EVENT_HTTP_CONTINUE);
}
static int
test_plugin (TSCont contp, TSEvent event, void *edata) {
TSHttpTxn txnp = (TSHttpTxn) edata;
switch (event) {
case TS_EVENT_HTTP_READ_REQUEST_HDR:
txn_handler (txnp, contp);
return 0;
default:
break;
}
return 0;
}
The mutex functions are listed below:
- :c:func:`TSMutexCreate`
- :c:func:`TSMutexLock`
- :c:func:`TSMutexLockTry`