Skip to content

Commit 6cb5d8b

Browse files
author
Julien Pauli
committed
php lifecycle pass two
1 parent 79e0ecd commit 6cb5d8b

File tree

7 files changed

+110
-17
lines changed

7 files changed

+110
-17
lines changed

Book/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ This book is currently being updated for PHP 7. Check back soon...
3737
php7/build_system.rst
3838
php7/internal_types.rst
3939
php7/extensions_design.rst
40-
40+
php7/memory_management.rst
4141
..
4242
php7/hashtables.rst
4343
php7/classes_objects.rst

Book/php7/extensions_design.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ Contents:
1111
.. toctree::
1212
:maxdepth: 2
1313

14-
extensions_design/php_lifecycle.rst
14+
extensions_design/php_lifecycle.rst
15+
extensions_design/globals_management.rst
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Managing global scope
2+
=====================
3+
4+

Book/php7/extensions_design/php_lifecycle.rst

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ sequence of its module which PHP is one. Starting up, is called internally **the
1010
abbreviate it as the **MINIT** step.
1111

1212
Once started, PHP waits to treat one/several requests. When we talk about PHP CLI, there will be only one request: the
13-
current script to run. However, when we talk about a web environment- should it be thought as PHP-FPM or webserver
13+
current script to run. However, when we talk about a web environment- should it be PHP-FPM or webserver
1414
module- PHP could serve several requests one after the other. It all depends on how you did configure you webserver:
1515
you may tell it to serve an infinite number of requests, or a specific number before shutting down and recycling the
1616
process. Everytime a new request shows in to be treated, PHP will run **a request startup step**. We call it the
@@ -41,9 +41,11 @@ To treat several requests at the same time, you need to run a parallelism model.
4141

4242
Using the process-based model, every PHP interpreter is isolated by the OS into its own process.
4343
This model is very common under Unix. Every request leaves into its own process.
44+
This model is used by PHP-CLI, PHP-FPM and PHP-CGI.
4445

4546
With the thread-based model, every PHP interpreter is isolated into a thread, using a thread library.
46-
This model is mainly used under Microsoft Windows OS, but can be used with most Unixes as well.
47+
This model is mainly used under Microsoft Windows OS, but can be used with most Unixes as well. This requires PHP and
48+
its extensions :doc:`to be built <../build_system/building_extensions>` in ZTS mode.
4749

4850
Here is the process-based model:
4951

@@ -55,14 +57,18 @@ And here is the thread-based model:
5557
.. image:: ./images/php_lifetime_thread.png
5658
:align: center
5759

60+
.. note:: PHP's multi-processing module is not of your choice, as an extension developer. You will have to support it.
61+
You will have to support the fact that your extension could run in a threaded environment, especially under
62+
Windows platforms, and you'll have to program against it.
63+
5864
The PHP extensions hooks
5965
************************
6066

61-
As you could have guessed, the PHP engine will trigger your extension at several lifetime points. We call those hook
62-
functions. Your extension may declare interest into specific lifetime points by declaring function hooks while it
67+
As you could have guessed, the PHP engine will trigger your extension at several lifetime points. We call those *hook
68+
functions*. Your extension may declare interest into specific lifetime points by declaring function hooks while it
6369
registers against the engine.
6470

65-
Those hooks can clearly be felt once you analyze a PHP extension structure, the ``zend_module_entry`` structure::
71+
Those hooks can clearly be noticed once you analyze a PHP extension structure, the ``zend_module_entry`` structure::
6672

6773
struct _zend_module_entry {
6874
unsigned short size;
@@ -102,11 +108,12 @@ Module initialization: MINIT()
102108

103109
This is PHP process startup step. In your extension's ``MINIT()``, you'll load and allocate any persistent object or
104110
piece of information you'll need for every future request.
111+
For the big part of them, those allocations will target read-only objects.
105112

106-
In ``MINIT()``, no thread has popped yet, so you may access global variables trully, with no protection at all. Also,
107-
you must not allocate memory that is request-bound, as a request has not started yet.
108-
You never use Zend Memory Manager allocations in ``MINIT()`` steps, but persistent allocations. No ``emalloc()``, but
109-
``pemalloc()``. Failing to do that will lead to crashes.
113+
In ``MINIT()``, no thread or process has popped yet, so you may fully access global variables with no protection at
114+
all. Also, you must not allocate memory that is request-bound, as a request has not started yet.
115+
You never use :doc:`Zend Memory Manager <../memory_management/zend_memory_manager>` allocations in ``MINIT()`` steps,
116+
but persistent allocations. No ``emalloc()``, but ``pemalloc()``. Failing to do that will lead to crashes.
110117

111118
At ``MINIT()``, the execution engine is not started yet, so beware of not trying to access any of its structure without
112119
special care.
@@ -115,7 +122,12 @@ If you need to register INI entries for your extension, ``MINIT()`` is the right
115122

116123
If you need to register read-only ``zend_strings`` for further usage, it is time to do so here (with persistent alloc).
117124

118-
.. note:: Remember that ``MINIT()`` is called only once in PHP process lifetime: when this later starts.
125+
If you need to allocate objects that well be written to while serving a request, then you'll need to duplicate their
126+
memory allocation to a thread-specific pool for the request. Remember that you can only write safely to global space
127+
while into ``MINIT()``.
128+
129+
.. note:: Memory management, allocations, and debugging; are part of the :doc:`memory management<../memory_management>`
130+
chapter.
119131

120132
``MINIT()`` is triggered by ``zend_startup_modules()`` in
121133
`php_module_startup() <https://github.com/php/php-src/blob/3704947696fe0ee93e025fa85621d297ac7a1e4d/main/main.c#L2009>`_
@@ -130,8 +142,9 @@ This is PHP process shutdown step. Easy enough, you basically perform here the e
130142
Take care again here: the execution engine is shut down, so you should not access any of its variable (but you should
131143
not need to here).
132144

133-
As you don't live in a request here, you should not free resources using Zend Memory Manager ``efree()`` or alikes, but
134-
free for persistent allocations, aka ``pefree()``.
145+
As you don't live in a request here, you should not free resources using
146+
:doc:`Zend Memory Manager <../memory_management/zend_memory_manager>` ``efree()`` or alikes, but free for persistent
147+
allocations, aka ``pefree()``.
135148

136149
``MSHUTDOWN()`` is triggered by ``zend_destroy_modules()`` from ``zend_shutdown()`` in
137150
`php_module_shutdown() <https://github.com/php/php-src/blob/3704947696fe0ee93e025fa85621d297ac7a1e4d/main/main.c#L2335>`_
@@ -141,7 +154,8 @@ Request initialization: RINIT()
141154
-------------------------------
142155

143156
A request just showed in, and PHP is about to treat it here. In ``RINIT()``, you bootstrap the resources you need to
144-
treat that precise request. PHP is a share-nothing architecture, and as-is, it provides memory management facilities.
157+
treat that precise request. PHP is a share-nothing architecture, and as-is, it provides
158+
:doc:`memory management <../memory_management>` facilities.
145159

146160
In ``RINIT()``, if you need to allocate dynamic memory, you'll use Zend Memory Manager. You will call for ``emalloc()``.
147161
Zend Memory Manager tracks the memory you allocate through it, and when the request shuts down, it will attempt to free
@@ -151,6 +165,13 @@ You should not require persistent dynamic memory here, aka libc's ``malloc()`` o
151165
persistent memory here, and forgets to free it, you'll create leaks that will stack as PHP treats more and more
152166
requests, to finally crash the process (Kernel OOM) and starve the machine memory.
153167

168+
Also, take really care not to write to global space here. If PHP is run into a thread as chosen parallelism model, then
169+
you'll modify the context for every thread of the pool (every other request treated in parallel to yours) and you could
170+
also trigger race conditions if you don't lock the memory.
171+
If you need globals, you'll need to protect them.
172+
173+
.. note:: Global scope management is explained into :doc:`a dedicated chapter <globals_management>`.
174+
154175
``RINIT()`` is triggered by ``zend_activate_module()`` in
155176
`php_request_startup() <https://github.com/php/php-src/blob/3704947696fe0ee93e025fa85621d297ac7a1e4d/main/main.c#L1558>`_
156177
function.
@@ -205,7 +226,7 @@ Globals management will be covered in its dedicated chapter.
205226
Remember that globals are not cleared after every request. If you need to reset them for every new request (likely),
206227
then you need to put such a procedure into ``RINIT()``.
207228

208-
.. todo: Add a chapter about globals management and ZTS
229+
.. note:: Global scope management is explained into :doc:`a dedicated chapter <globals_management>`.
209230

210231
Globals termination: GSHUTDOWN()
211232
--------------------------------
@@ -220,7 +241,7 @@ Globals management will be covered in its dedicated chapter.
220241

221242
Remember that globals are not cleared after every request; aka ``GSHUTDOWN()`` is not called as part of ``RSHUTDOWN()``.
222243

223-
.. todo: Add a chapter about globals management and ZTS
244+
.. note:: Global scope management is explained into :doc:`a dedicated chapter <globals_management>`.
224245

225246
Thoughts on PHP lifecycle
226247
-------------------------
@@ -245,3 +266,54 @@ threads as multi-processing engine, then you crash every other thread with you,
245266
Hooking by overwritting function pointers
246267
*****************************************
247268

269+
Now you know when the engine will trigger your code, there exists also noticeable function pointers you may replace to
270+
hook into the engine.
271+
As those pointers are global variables, you may replace them into ``MINIT()`` step, and put them back into
272+
``MSHUTDOWN()``.
273+
274+
Those of interest are:
275+
276+
.. +---------+-----------------+---------------------------------------------------------------------------+
277+
.. | Subject | Definition file | function |
278+
.. +---------+-----------------+---------------------------------------------------------------------------+
279+
.. | Error | Zend/zend.h | `void (*zend_error_cb)(int type, const char *error_filename, |
280+
.. | | | const uint error_lineno, const char *format, va_list args)` |
281+
.. +---------+-----------------+---------------------------------------------------------------------------+
282+
.. | | | |
283+
.. +---------+-----------------+---------------------------------------------------------------------------+
284+
285+
* AST, Zend/zend_ast.h:
286+
* `void (*zend_ast_process_t)(zend_ast *ast)`
287+
288+
* Compiler, Zend/zend_compile.h:
289+
* `zend_op_array *(*zend_compile_file)(zend_file_handle *file_handle, int type)`
290+
* `zend_op_array *(*zend_compile_string)(zval *source_string, char *filename)`
291+
292+
* Executor, Zend/zend_execute.h:
293+
* `void (*zend_execute_ex)(zend_execute_data *execute_data)`
294+
* `void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value)`
295+
296+
* GC, Zend/zend_gc.h:
297+
* `int (*gc_collect_cycles)(void)`
298+
299+
* TSRM, TSRM/TSRM.h:
300+
* `void (*tsrm_thread_begin_func_t)(THREAD_T thread_id)`
301+
* `void (*tsrm_thread_end_func_t)(THREAD_T thread_id)`
302+
303+
* Error, Zend/zend.h:
304+
* `void (*zend_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format,
305+
va_list args)`
306+
307+
* Exceptions, Zend/zend_exceptions.h:
308+
* `void (*zend_throw_exception_hook)(zval *ex)`
309+
310+
* Lifetime, Zend/zend.h:
311+
* `void (*zend_on_timeout)(int seconds)`
312+
* `void (*zend_interrupt_function)(zend_execute_data *execute_data)`
313+
* `void (*zend_ticks_function)(int ticks)`
314+
315+
Other exists but the above ones are the most important ones you could need while designing PHP extensions.
316+
As their names are self explanatory, there is no need to detail every of them.
317+
318+
If you need some more informations, you can look for them into PHP source code, and discover when and how they get
319+
triggered.

Book/php7/memory_management.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Memory management
2+
=================
3+
4+
Contents:
5+
6+
.. toctree::
7+
:maxdepth: 2
8+
9+
memory_management/zend_memory_manager.rst
10+
memory_management/memory_debugging.rst
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Debugging memory
2+
================
3+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Zend Memory Manager
2+
===================
3+

0 commit comments

Comments
 (0)