|
| 1 | +.. _app-yields: |
| 2 | + |
| 3 | +Yields |
| 4 | +====== |
| 5 | + |
| 6 | +Any live fiber can be in one of three states: ``running``, ``suspended``, and |
| 7 | +``ready``. After a fiber dies, the ``dead`` status returns. By observing |
| 8 | +fibers from the outside, you can only see ``running`` (for the current fiber) |
| 9 | +and ``suspended`` for any other fiber waiting for an event from eventloop (``ev``) |
| 10 | +for execution. |
| 11 | + |
| 12 | + |
| 13 | +.. image:: yields.svg |
| 14 | + :align: center |
| 15 | + |
| 16 | + |
| 17 | +Yield is an action that occurs in a :ref:`cooperative <app-cooperative_multitasking>` environment that |
| 18 | +transfers control of the thread from the current fiber to another fiber that is ready to execute. |
| 19 | + |
| 20 | + |
| 21 | +After yield has occurred, the next ``ready`` fiber is taken from the queue and executed. |
| 22 | +When there are no more ``ready`` fibers, execution is transferred to the event loop. |
| 23 | + |
| 24 | +After a fiber has yielded and regained control, it immediately issues :ref:`testcancel <fiber-testcancel>`. |
| 25 | + |
| 26 | +There are :ref:`explicit <app-explicit-yields>` and :ref:`implicit <app-implicit-yields>` yields. |
| 27 | + |
| 28 | +.. _app-explicit-yields: |
| 29 | + |
| 30 | +Explicit yields |
| 31 | +--------------- |
| 32 | + |
| 33 | +**Explicit yield** is clearly visible from the invoking code. There are only two |
| 34 | +explicit yields: :ref:`fiber.yield() <fiber-yield>` and :ref:`fiber.sleep(t) <fiber-sleep>` |
| 35 | + |
| 36 | +:ref:`fiber.yield() <fiber-yield>` yields execution to another ``ready`` fiber while putting itself in the ``ready`` state, |
| 37 | +meaning that it will be executed again as soon as possible while being polite to other fibers |
| 38 | +waiting for execution. |
| 39 | + |
| 40 | +:ref:`fiber.sleep(n) <fiber-sleep>` yields execution to another ``ready`` fiber and puts itself in the ``suspended`` |
| 41 | +state for time ``t`` until time pass and the event loop wakes up that fiber to the ``ready`` state. |
| 42 | + |
| 43 | +In general, it is good behavior for long-running cpu-intensive tasks to yield periodically to |
| 44 | +be :ref:`cooperative <app-cooperative_multitasking>` to other waiting fibers. |
| 45 | + |
| 46 | +.. _app-implicit-yields: |
| 47 | + |
| 48 | +Implicit yields |
| 49 | +--------------- |
| 50 | + |
| 51 | +On the other hand, there are many operations, such as operations with sockets, file system, |
| 52 | +and disk I/O, which imply some waiting for the current fiber while others can be |
| 53 | +executed. When such an operation occurs, a possible blocking operation would be passed into the |
| 54 | +event loop and the fiber would be suspended until the resource is ready to |
| 55 | +continue fiber execution. |
| 56 | + |
| 57 | +Here is the list of implicitly yielding operations: |
| 58 | + |
| 59 | +* Connection establishment (:ref:`socket <socket-module>`). |
| 60 | + |
| 61 | +* Socket read and write (:ref:`socket <socket-module>`). |
| 62 | + |
| 63 | +* Filesystem operations (from :ref:`fio <fio-section>`). |
| 64 | + |
| 65 | +* Channel data transfer (:ref:`fiber.channel <fiber-channel>`). |
| 66 | + |
| 67 | +* File input/output (from :ref:`fio <fio-section>`). |
| 68 | + |
| 69 | +* Console operations (since console is a socket). |
| 70 | + |
| 71 | +* HTTP requests (since HTTP is a socket operation). |
| 72 | + |
| 73 | +* Database modifications (if they imply a disk write). |
| 74 | + |
| 75 | +* Database reading for the :ref:`vinyl <engines-chapter>` engine. |
| 76 | + |
| 77 | +* Invocation of another process (:ref:`popen <popen-module>`). |
| 78 | + |
| 79 | +.. note:: |
| 80 | + |
| 81 | + Please note that all operations of ``os`` mosule are non-cooperative and |
| 82 | + exclusively block the whole tx thread. |
| 83 | + |
| 84 | +For :ref:`memtx <engines-chapter>`, since all data is in memory, there is no yielding for a read requests |
| 85 | +(like ``:select``, ``:pairs``, ``:get``). |
| 86 | + |
| 87 | +For :ref:`vinyl <engines-chapter>`, since some data may not be in memory, there may be disk I/O for a |
| 88 | +read (to fetch data from disk) or write (because a stall may occur while waiting for memory to be freed). |
| 89 | + |
| 90 | +For both :ref:`memtx <engines-chapter>` and :ref:`vinyl <engines-chapter>`, since data change requests |
| 91 | +must be recorded in the :ref:`WAL <internals-wal>`, there is normally a :doc:`/reference/reference_lua/box_txn_management/commit`. |
| 92 | + |
| 93 | +With the default ``autocommit`` mode the following operations are yielding: |
| 94 | + |
| 95 | +* :ref:`space:alter <box_space-alter>`. |
| 96 | + |
| 97 | +* :ref:`space:drop <box_space-drop>`. |
| 98 | + |
| 99 | +* :ref:`space:create_index <box_space-create_index>`. |
| 100 | + |
| 101 | +* :ref:`space:truncate <box_space-truncate>`. |
| 102 | + |
| 103 | +* :ref:`space:insert <box_space-insert>`. |
| 104 | + |
| 105 | +* :ref:`space:replace <box_space-replace>`. |
| 106 | + |
| 107 | +* :ref:`space:update <box_space-update>`. |
| 108 | + |
| 109 | +* :ref:`space:upserts <box_space-upsert>`. |
| 110 | + |
| 111 | +* :ref:`space:delete <box_space-delete>`. |
| 112 | + |
| 113 | +* :ref:`index:update <box_index-update>`. |
| 114 | + |
| 115 | +* :ref:`index:delete <box_index-delete>`. |
| 116 | + |
| 117 | +* :ref:`index:alter <box_index-alter>`. |
| 118 | + |
| 119 | +* :ref:`index:drop <box_index-drop>`. |
| 120 | + |
| 121 | +* :ref:`index:rename <box_index-rename>`. |
| 122 | + |
| 123 | +* :ref:`box.commit <box-commit>` (*if there were some modifications within the transaction*). |
| 124 | + |
| 125 | +To provide atomicity for transactions in transaction mode, some changes are applied to the |
| 126 | +modification operations for the :ref:`memtx <engines-chapter>` engine. After executing |
| 127 | +:ref:`box.begin <box-begin>` or within a :ref:`box.atomic <box-atomic>` |
| 128 | +call, any modification operation will not yield, and yield will occur only on :ref:`box.commit <box-commit>` or upon return |
| 129 | +from :ref:`box.atomic <box-atomic>`. Meanwhile, :ref:`box.rollback <box-rollback>` does not yield. |
| 130 | + |
| 131 | +That is why executing separate commands like ``select()``, ``insert()``, ``update()`` in the console inside a |
| 132 | +transaction without MVCC will cause it to an abort. This is due to implicit yield after each |
| 133 | +chunk of code is executed in the console. |
| 134 | + |
| 135 | + |
| 136 | +**Example #1** |
| 137 | + |
| 138 | +* Engine = memtx. |
| 139 | + |
| 140 | +.. code-block:: memtx |
| 141 | +
|
| 142 | + space:get() |
| 143 | + space:insert() |
| 144 | +
|
| 145 | +
|
| 146 | +The sequence has one yield, at the end of the insert, caused by implicit commit; |
| 147 | +``get()`` has nothing to write to the :ref:`WAL <internals-wal>` and so does not yield. |
| 148 | + |
| 149 | +* Engine = memtx. |
| 150 | + |
| 151 | +.. code-block:: memtx |
| 152 | +
|
| 153 | + box.begin() |
| 154 | + space1:get() |
| 155 | + space1:insert() |
| 156 | + space2:get() |
| 157 | + space2:insert() |
| 158 | + box.commit() |
| 159 | +
|
| 160 | +
|
| 161 | +The sequence has one yield, at the end of the ``box.commit``, none of the inserts are yielding. |
| 162 | + |
| 163 | +* Engine = vinyl. |
| 164 | + |
| 165 | +.. code-block:: vinyl |
| 166 | +
|
| 167 | + space:get() |
| 168 | + space:insert() |
| 169 | +
|
| 170 | +
|
| 171 | +The sequence has one to three yields, since ``get()`` may yield if the data is not in the cache, |
| 172 | +``insert()`` may yield if it waits for available memory, and there is an implicit yield |
| 173 | +at commit. |
| 174 | + |
| 175 | +* Engine = vinyl. |
| 176 | + |
| 177 | +.. code-block:: vinyl |
| 178 | +
|
| 179 | + box.begin() |
| 180 | + space1:get() |
| 181 | + space1:insert() |
| 182 | + space2:get() |
| 183 | + space2:insert() |
| 184 | + box.commit() |
| 185 | +
|
| 186 | +
|
| 187 | +The sequence may yield from 1 to 5 times. |
| 188 | + |
| 189 | + |
| 190 | +**Example #2** |
| 191 | + |
| 192 | +Assume that there are tuples in the :ref:`memtx <engines-chapter>` space ``tester`` where the third field |
| 193 | +represents a positive dollar amount. |
| 194 | + |
| 195 | + |
| 196 | +Let's start a transaction, withdraw from tuple#1, deposit in tuple#2, and end |
| 197 | +the transaction, making its effects permanent. |
| 198 | + |
| 199 | +.. code-block:: tarantoolsession |
| 200 | +
|
| 201 | + tarantool> function txn_example(from, to, amount_of_money) |
| 202 | + > box.atomic(function() |
| 203 | + > box.space.tester:update(from, {{'-', 3, amount_of_money}}) |
| 204 | + > box.space.tester:update(to, {{'+', 3, amount_of_money}}) |
| 205 | + > end) |
| 206 | + > return "ok" |
| 207 | + > end |
| 208 | + |
| 209 | + Result: |
| 210 | + --- |
| 211 | + ... |
| 212 | + tarantool> txn_example({999}, {1000}, 1.00) |
| 213 | + --- |
| 214 | + - "ok" |
| 215 | + ... |
| 216 | +
|
| 217 | +If :ref:`wal_mode <cfg_binary_logging_snapshots-wal_mode>` = ``none``, then |
| 218 | +there is no implicit yielding at the commit time because there are |
| 219 | +no writes to the :ref:`WAL <internals-wal>`. |
| 220 | + |
| 221 | +If a request if performed via network connector such as :ref:`net.box <net_box-module>` and implies |
| 222 | +sending requests to the server and receiving responses, then it involves network |
| 223 | +I/O and thus implicit yielding. Even if the request that is sent to the server |
| 224 | +has no implicit yield. Therefore, the following sequence causes yields |
| 225 | +three times sequentially when sending requests to the network and awaiting the results. |
| 226 | + |
| 227 | + |
| 228 | +.. cssclass:: highlight |
| 229 | +.. parsed-literal:: |
| 230 | +
|
| 231 | + conn.space.test:get{1} |
| 232 | + conn.space.test:get{2} |
| 233 | + conn.space.test:get{3} |
0 commit comments