rfc:engine_exceptions

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
rfc:engine_exceptions [2013/10/23 18:23] – created nikicrfc:engine_exceptions [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 2: Line 2:
   * Date: 2013-10-23   * Date: 2013-10-23
   * Author: Nikita Popov <nikic@php.net>   * Author: Nikita Popov <nikic@php.net>
-  * Status: In Draft+  * Status: Declined for 5.6
   * Proposed for: PHP 5.6   * Proposed for: PHP 5.6
   * Patch: https://github.com/nikic/php-src/compare/engineExceptions   * Patch: https://github.com/nikic/php-src/compare/engineExceptions
 +  * ML discussion: http://markmail.org/message/uemuxsk3vk46nedo
  
 ===== Introduction ===== ===== Introduction =====
Line 10: Line 11:
 This RFC proposes to allow the use of exceptions in the engine and to allow the replacement of existing fatal or recoverable fatal errors with exceptions. This RFC proposes to allow the use of exceptions in the engine and to allow the replacement of existing fatal or recoverable fatal errors with exceptions.
  
-As an example of this change, consider the following the following code-snippet:+As an example of this change, consider the following code-snippet:
  
 <code php> <code php>
Line 81: Line 82:
 </code> </code>
  
-The first five errors are fatal, i.e. they will not invoke the error handler, abort execution in the current context and directly jump (bailout) to the shutdown procedure. After a fatal error shutdown functions and destructors are still run.+The first five errors are fatal, i.e. they will not invoke the error handler, abort execution in the current context and directly jump (bailout) to the shutdown procedure.
  
 The ''E_RECOVERABLE_ERROR'' error type behaves like a fatal error by default, but it will invoke the error handler, which can instruct the engine to ignore the error and continue execution in the context where the error was raised. The ''E_RECOVERABLE_ERROR'' error type behaves like a fatal error by default, but it will invoke the error handler, which can instruct the engine to ignore the error and continue execution in the context where the error was raised.
Line 91: Line 92:
 === Cannot be gracefully handled === === Cannot be gracefully handled ===
  
-The most obvious issue with fatal errors is that they immidiately abort execution and as such cannot be gracefully recovered from, which is very problematic in some situations.+The most obvious issue with fatal errors is that they immediately abort execution and as such cannot be gracefully recovered from. This behavior is very problematic in some situations.
  
 As an example consider a server or daemon written in PHP. If a fatal error occurs during the handling of a request it will abort not only that individual request but kill the entire server/daemon. It would be much preferable to catch the fatal error and abort the request it originated from, but continue to handle other requests. As an example consider a server or daemon written in PHP. If a fatal error occurs during the handling of a request it will abort not only that individual request but kill the entire server/daemon. It would be much preferable to catch the fatal error and abort the request it originated from, but continue to handle other requests.
Line 116: Line 117:
 </code> </code>
  
-This allows rudimentary handling of fatal errors, but the available information is very limited. In particular the shutdown function is not able to retreive a stacktrace for the error (which is possible for other error types going through the error handler.)+This allows rudimentary handling of fatal errors, but the available information is very limited. In particular the shutdown function is not able to retrieve a stacktrace for the error (which is possible for other error types going through the error handler.)
  
 === Finally blocks will not be invoked === === Finally blocks will not be invoked ===
Line 123: Line 124:
  
 <code php> <code php>
-$lock->aquire();+$lock->acquire();
 try { try {
     doSomething();     doSomething();
Line 131: Line 132:
 </code> </code>
  
-If ''doSomething()'' in the above example results in a fatal error the finally block will not be run and the lock is not released.+If ''doSomething()'' in the above example results in a fatal error the ''finally'' block will not be run and the lock is not released.
  
 === Destructors are not called === === Destructors are not called ===
  
-When a fatal error occurs destructors are not invoked. This means that anything relying on the RAII (Resource Aquisition Is Initialization) will break. Using the lock example again:+When a fatal error occurs destructors are not invoked. This means that anything relying on the RAII (Resource Acquisition Is Initialization) will break. Using the lock example again:
  
 <code php> <code php>
Line 142: Line 143:
     public function __construct(Lock $lock) {     public function __construct(Lock $lock) {
         $this->lock = $lock;         $this->lock = $lock;
-        $this->lock->aquire();+        $this->lock->acquire();
     }     }
     public function __destruct() {     public function __destruct() {
Line 150: Line 151:
  
 function test($lock) { function test($lock) {
-    $manager = new LockManager($lock); // aquire lock+    $manager = new LockManager($lock); // acquire lock
          
     doSomething();     doSomething();
Line 174: Line 175:
 === Hard to catch === === Hard to catch ===
  
-While ''E_RECOVERABLE_ERROR'' is presented as a "Catchable fatal error" to the end user the error is actually rather hard to catch. In particular the familiar ''try''/''catch'' structure cannot be used and instead an error handler needs to be employed.+While ''E_RECOVERABLE_ERROR'' is presented as a "Catchable fatal error" to the end userthe error is actually rather hard to catch. In particular the familiar ''try''/''catch'' structure cannot be used and instead an error handler needs to be employed.
  
 To catch a recoverable fatal error non-intrusively code along the following lines is necessary: To catch a recoverable fatal error non-intrusively code along the following lines is necessary:
  
 <code php> <code php>
-$oldErrorHandler = set_error_handler(function($errno, $errstr, $errfile, $errline) { +set_error_handler(function($errno, $errstr, $errfile, $errline) { 
- if ($errno === E_RECOVERABLE_ERROR) { +    if ($errno === E_RECOVERABLE_ERROR) { 
- throw new ErrorException($errstr, $errno, 0, $errfile, $errline); +        throw new ErrorException($errstr, $errno, 0, $errfile, $errline); 
-+    
- return false;+    return false;
 }); });
  
Line 189: Line 190:
     new Closure;     new Closure;
 } catch (Exception $e) { } catch (Exception $e) {
- echo "Caught: {$e->getMessage()}\n";+    echo "Caught: {$e->getMessage()}\n";
 } }
  
-set_error_handler($oldErrorHandler);+restore_error_handler();
 </code> </code>
  
 ==== Solution: Exceptions ==== ==== Solution: Exceptions ====
  
-Exceptions provide an approach to error handling that does not suffer from the problems of fatal errors and recoverable errors. In particular exceptions can be gracefully handled, they will invoke ''finally'' blocks and destructors and are easily caught using ''catch'' blocks.+Exceptions provide an approach to error handling that does not suffer from the problems of fatal and recoverable fatal errors. In particular exceptions can be gracefully handled, they will invoke ''finally'' blocks and destructors and are easily caught using ''catch'' blocks.
  
-From an implementational point of view they also form a middle group between fatal errors (abort execution) and recoverable fatal errors (continue in the same codepath). Exception typically leave the current codepath right away and make use of automatic cleanup mechanisms (e.g. there is no need to manually clean up the stack). In order to throw an exception from the VM you usually only need to free the opcode operands and invoke ''HANDLE_EXCEPTION()''.+From an implementational point of view they also form a middle ground between fatal errors (abort execution) and recoverable fatal errors (continue in the same codepath). Exceptions typically leave the current codepath right away and make use of automatic cleanup mechanisms (e.g. there is no need to manually clean up the stack). In order to throw an exception from the VM you usually only need to free the opcode operands and invoke ''HANDLE_EXCEPTION()''.
  
 Exceptions have the additional advantage of providing a stack trace. Exceptions have the additional advantage of providing a stack trace.
Line 222: Line 223:
 Internally the following APIs are added: Internally the following APIs are added:
  
-<code>+<code c>
 // Returns the class_entry for EngineException // Returns the class_entry for EngineException
 ZEND_API zend_class_entry *zend_get_engine_exception(TSRMLS_D); ZEND_API zend_class_entry *zend_get_engine_exception(TSRMLS_D);
Line 236: Line 237:
 </code> </code>
  
-Exceptions sometimes need to be thrown before all opcode operands are fetched. In this case the operands still need to freed, but the ordinary ''FREE_OP*'' VM pseudo-macros cannot be used. To solve several VM-macros/functions are introduced:+Exceptions sometimes need to be thrown before all opcode operands have been fetched. In this case the operands still need to freed, but the ordinary ''FREE_OP*'' VM pseudo-macros cannot be used. To solve this several VM-macros/functions are introduced:
  
-<code> +<code c
-// optype-specialized pseudo-macros+// Optype-specialized pseudo-macros
 FREE_UNFETCHED_OP1(); FREE_UNFETCHED_OP1();
 FREE_UNFETCHED_OP2(); FREE_UNFETCHED_OP2();
  
-// Used for frees in two-op instructions+// Used for frees in multi-opline instructions
 static zend_always_inline void _free_unfetched_op(int op_type, znode_op *op, const zend_execute_data *execute_data TSRMLS_DC); static zend_always_inline void _free_unfetched_op(int op_type, znode_op *op, const zend_execute_data *execute_data TSRMLS_DC);
 </code> </code>
  
-Furthermore the patch accompanying this RFC also contains initial work for moving off ''E_ERROR''/''E_RECOVERABLE_ERROR''. In particular it removes all uses of ''E_ERROR'' in ''zend_vm_def.h''.+Furthermore the patch accompanying this RFC contains initial work for replacing existing ''E_ERROR''/''E_RECOVERABLE_ERROR'' errors with exceptions. In particular it removes all uses of ''E_ERROR'' in ''zend_vm_def.h''.
  
 ===== Potential issues ===== ===== Potential issues =====
Line 255: Line 256:
 Currently it is possible to silently ignore recoverable fatal errors with a custom error handler. By replacing them with exceptions this capability is removed, thus breaking compatibility. Currently it is possible to silently ignore recoverable fatal errors with a custom error handler. By replacing them with exceptions this capability is removed, thus breaking compatibility.
  
-I have never seen this possibility used in practice outside some weird hacks (which use ignored recoverable type constraint errors to implement scalar typehints). In most cases custom error handlers throw an ''ErrorException'', in which case it emulates the proposed behavior but using a different exception type.+I have never seen this possibility used in practice outside some weird hacks (which use ignored recoverable type constraint errors to implement scalar typehints). In most cases custom error handlers throw an ''ErrorException'', i.e. they emulate the proposed behavior with a different exception type.
  
 If these concerns are considered significant this RFC might be restricted to ''E_ERROR'' conversions only. Personally I doubt that this will result in any significant breakage, but I can't claim extensive knowledge in this area. If these concerns are considered significant this RFC might be restricted to ''E_ERROR'' conversions only. Personally I doubt that this will result in any significant breakage, but I can't claim extensive knowledge in this area.
Line 261: Line 262:
 ==== catch-all blocks in existing code ==== ==== catch-all blocks in existing code ====
  
-As ''EngineException'' extends ''Exception'' it will be caught by catch-blocks of type ''catch (Exception)''. This may cause to cause existing code to inadvertantly catch engine exceptions.+As ''EngineException'' extends ''Exception'' it will be caught by catch-blocks of type ''catch (Exception)''. This may cause existing code to inadvertently catch engine exceptions.
  
-If this is considered to be an issue solution is to introduce a ''BaseException'' with ''Exception extends BaseException'', which will be the new base of the exception hierarchy. This exception type would be used only for exception types that are "unlikely to require catchingin anything save top-level handlers. Both Python (''BaseException'') and Java (''Throwable'') make use of this concept.+If this is considered to be an issue one possible solution is to introduce a ''BaseException'' with ''Exception extends BaseException'', which will be the new base of the exception hierarchy. Only exceptions that are considered unlikely to require catching in anything save top-level handlers will directly inherit from this type. Both Python (''BaseException'') and Java (''Throwable'') make use of this concept.
  
 ''EngineException'' could then extend ''BaseException'' rather than ''Exception''. ''EngineException'' could then extend ''BaseException'' rather than ''Exception''.
Line 297: Line 298:
 </code> </code>
  
-Additional improvement (like removing the ''Fatal error:'' prefix and the duplicate file/line information would require special handling in ''zend_error'':+Additional improvement (like removing the ''Fatal error:'' prefix and the duplicate file/line informationwould require special handling in ''zend_error'':
  
 <code> <code>
Line 305: Line 306:
 #1 {main} #1 {main}
 </code> </code>
 +
 +==== Not all errors converted ====
 +
 +The Zend Engine currently (master on 2013-12-10) contains the following number of fatal-y errors:
 +
 +<code>
 +E_ERROR:            183    (note: not counting 538 occurrences in zend_vm_execute.h)
 +E_CORE_ERROR:        12
 +E_COMPILE_ERROR:    151
 +E_PARSE:              1
 +E_RECOVERABLE_ERROR: 14
 +</code>
 +
 +The count was obtained using ''git grep "error[^(]*(E_ERROR_TYPE" Zend | wc -l'' and as such may not be totally accurate, but should be a good approximation.
 +
 +The patch attached to the RFC currently (as of 2013-10-24) removes 70 ''E_ERROR''s and 11 ''E_RECOVERABLE_ERROR''s. While I hope to port more errors to exceptions before the patch is merged, the process is rather time consuming and I will not be able to convert all errors. (Note: The number of occurrences in the source code says rather little about what percentage of "actually thrown" errors this constitutes.)
 +
 +Some errors are easy to change to exceptions, others are more complicated. Some are impossible, like the memory limit or execution time limit errors. The ''E_CORE_ERROR'' type can't be converted to use exceptions because it occurs during startup (at least if used correctly). ''E_COMPILE_ERROR'' (and ''E_PARSE'') currently also can't be converted to exceptions, due to concerns regarding global state modifications.
 +
 +Converting most existing errors will take some time and in the meantime we'll be in a situation where some part of the errors were converted to exceptions but another part stays fatal. From a user perspective it may not be immediately clear when one is used over the other.
 +
 +While this may be slightly inconvenient, I strongly think that it's better to start fixing this gradually, rather than waiting until the time (that will never come) where we can fix everything at once.
 +
 +===== Backwards compatibility =====
 +
 +The ''E_ERROR'' portion of this proposal does not break backwards compatibility: All code that was previously working, will continue to work. The change only relaxes error conditions, which is generally not regarded as breaking BC.
 +
 +The ''E_RECOVERABLE_ERROR'' part of the proposal may introduce a minor BC break, because it will no longer allow to silently ignore recoverable errors with a custom error handler. As this point is somewhat controversial I'll have a separate voting option for this.
  
 ===== Patch ===== ===== Patch =====
Line 310: Line 339:
 A preliminary patch for this RFC is available at https://github.com/nikic/php-src/compare/engineExceptions. A preliminary patch for this RFC is available at https://github.com/nikic/php-src/compare/engineExceptions.
  
-The patch introduces basic infrastructure for this change and removes all ''E_ERROR'' uses from ''zend_vm_def.h'' (as well as some other errors). This is just preliminary work and hopefully more errors can be covered by the time this is merged.+The patch introduces basic infrastructure for this change and removes all ''E_ERROR'' uses from ''zend_vm_def.h'' (as well as some other errors). 
 + 
 +===== Vote ===== 
 + 
 +This is a yes/no vote with the additional option of implementing the proposal, but without changing ''E_RECOVERABLE_ERROR''s (as that part of the proposal may have BC issues). 
 + 
 +As this is a language-related change, the vote requires a two-third majority. The 3-way vote will be interpreted as follows: If 2/3 of the total votes are for "Yes", the proposal is implemented fully. If 2/3 of the votes are for "Yes" or "Yes, without E_RECOVERABLE_ERROR changes" then the proposal is implemented without the ''E_RECOVERABLE_ERROR'' changes. Otherwise, the proposal is not implemented. 
 + 
 +If you are in favor of this proposal in general, but not for PHP 5.6, use the "No" voting option. 
 + 
 +<doodle title="Implement in PHP 5.6?" auth="nikic" voteType="single" closed="true"> 
 +   * Yes 
 +   * Yes, without E_RECOVERABLE_ERROR changes 
 +   * No 
 +</doodle> 
 + 
 +Vote started on 2013-12-07 and ended on 2013-12-14.
rfc/engine_exceptions.1382552616.txt.gz · Last modified: 2017/09/22 13:28 (external edit)