rfc:retry-keyword

Differences

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

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:retry-keyword [2017/06/19 13:22] – Add lots more examples & refine syntax sammykrfc:retry-keyword [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
-====== PHP RFC: Retry keyword in catch blocks ======+====== PHP RFC: Retry functionality ======
   * Version: 1.0   * Version: 1.0
   * Date: 2016-06-19   * Date: 2016-06-19
Line 19: Line 19:
   * Retry the ''try'' block ''n'' times or forever (in this example 3 times)   * Retry the ''try'' block ''n'' times or forever (in this example 3 times)
   * Execute arbitrary code before each retry (to sleep, log, check exception code, etc)   * Execute arbitrary code before each retry (to sleep, log, check exception code, etc)
-  * Access to the number of times the try block has been executed (with '$attempt'+  * Access to the number of times the try block has been executed (with ''$attempt'') 
-  * Use the ''break'' keyword to break out of retry attempts for reasons other than the 1) exception not being thrown or 2) running out of retry attempts; (more on this later)+  * Use the ''break'' keyword to break out of retry attempts for reasons other than 1) the exception not being thrown or 2) running out of retry attempts; (more on this later)
  
 <code php> <code php>
Line 26: Line 26:
     somethingSketchy();     somethingSketchy();
 } retry 3 (RecoverableException $e, $attempt) { } retry 3 (RecoverableException $e, $attempt) {
-    echo "Failed doing sketchy thing on try #{$attempt}. Retrying...\n";+    echo "Failed doing sketchy thing on try #{$attempt}. Retrying...";
     sleep(1);     sleep(1);
 } catch (RecoverableException $e) { } catch (RecoverableException $e) {
-    echo $e->getMessage().PHP_EOL;+    echo $e->getMessage();
 } }
 </code> </code>
Line 81: Line 81:
 </code> </code>
  
-There are currently a few ways to implement a feature that will retry a failed block of code ''x'' number of times.+There are currently a few ways to implement a feature that will retry a failed block of code ''n'' times.
  
 === Recursive Functions === === Recursive Functions ===
Line 87: Line 87:
 <code php> <code php>
 function myRetryFunction($maxTries) { function myRetryFunction($maxTries) {
- try { +    try { 
- somethingSketchy(); +        somethingSketchy(); 
- } catch (RecoverableException $e) { +    } catch (RecoverableException $e) { 
- if ($maxTries === 0) { +        if ($maxTries === 0) { 
- die('Tried a bunch but failed.'); +            die('Tried a bunch but failed.'); 
- +        
- myRetryFunction(--$maxTries); +        myRetryFunction(--$maxTries); 
- }+    }
 } }
  
Line 108: Line 108:
  
 for ($x=0; $x<=$maxTries; $x++) { for ($x=0; $x<=$maxTries; $x++) {
- try { +    try { 
- somethingSketchy(); +        somethingSketchy(); 
- break; +        break; 
- } catch (RecoverableException $e) { +    } catch (RecoverableException $e) { 
- die('Tried a bunch but failed.'); +        die('Tried a bunch but failed.'); 
- }+    }
 } }
 </code> </code>
Line 126: Line 126:
 retryTheThing: retryTheThing:
 try { try {
- somethingSketchy();+    somethingSketchy();
 } catch (RecoverableException $e) { } catch (RecoverableException $e) {
- if (--$maxTries > 0) { +    if (--$maxTries > 0) { 
- goto retryTheThing; +        goto retryTheThing; 
-+    
- die('Tried a bunch but failed.');+    die('Tried a bunch but failed.');
 } }
 </code> </code>
  
-This is arguably the cleanest option for retrying but is blanketed with the negative stigma of using a ''goto''.+While this is arguably the cleanest option, it still requires the developer to define and manage label which over several refactors might gradually move further away from the top of the ''try'' line. This implementation also makes it easy to accidentally execute any code after the label & before the ''try'' for each retry which is not entirely obvious at first glance.
  
-=== Use Retry ===+<code php> 
 +$maxTries = 5; 
 + 
 +retryTheThing: 
 + 
 +someCodeIDoNotWantToRetry(); 
 + 
 +try { 
 +    somethingSketchy(); 
 +} catch (RecoverableException $e) { 
 +    if (--$maxTries > 0) { 
 +        goto retryTheThing; 
 +    } 
 +    die('Tried a bunch but failed.'); 
 +
 +</code> 
 + 
 +==== Use Retry ====
  
 Using the ''retry'' keyword implementation allows a developer to use her own method of tracking the number of attempts as well as a way to execute any arbitrary code before trying again. Using the ''retry'' keyword implementation allows a developer to use her own method of tracking the number of attempts as well as a way to execute any arbitrary code before trying again.
Line 143: Line 160:
 <code php> <code php>
 const MAX_TRIES = 5; const MAX_TRIES = 5;
-$attempts = 0;+$attempt = 0;
 try { try {
- somethingSketchy();+    somethingSketchy();
 } catch (RecoverableException $e) { } catch (RecoverableException $e) {
- if ($attempt < MAX_TRIES) { +    if (++$attempt < MAX_TRIES) { 
- sleep(1); +        sleep(1); 
- // And log stuff maybe +        // And log stuff maybe 
- retry; +        retry; 
-+    
- die('Tried a bunch but failed.');+    die('Tried a bunch but failed.');
 } }
 </code> </code>
  
-Alternatively using the block-level implementation of ''retry'' keeps the developer from having to write her own boilerplate to both 1) track the number of attempts and 2) execute arbitrary code before retying.+Alternatively using the block-level implementation of ''retry'' keeps the developer from having to write her own boilerplate to both 1) track the number of attempts and 2) execute arbitrary code before retrying.
  
 <code php> <code php>
Line 179: Line 196:
 <code php> <code php>
 try { try {
- throw new RecoverableException("FAILED");+    throw new RecoverableException("FAILED");
 } retry 3 (RecoverableException | AnotherRecoverableException $e, $attempt) { } retry 3 (RecoverableException | AnotherRecoverableException $e, $attempt) {
-    echo "Failed on try #{$attempt}. Retrying...\n"; +    echo "Failed on try #{$attempt}. Retrying..."; 
- sleep(1);+    sleep(1);
 } catch (RecoverableException $e) { } catch (RecoverableException $e) {
-    echo $e->getMessage().PHP_EOL;+    echo $e->getMessage();
 } }
  
Line 196: Line 213:
 <code php> <code php>
 try { try {
- throw new RecoverableException("FAILED");+    throw new RecoverableException("FAILED");
 } retry 10 (RecoverableException $e, $attempt) { } retry 10 (RecoverableException $e, $attempt) {
     echo "Retrying...";     echo "Retrying...";
Line 210: Line 227:
 <code php> <code php>
 try { try {
- throw new RecoverableException("FAILED");+    throw new RecoverableException("FAILED");
 } retry (RecoverableException $e, $attempt) { } retry (RecoverableException $e, $attempt) {
     echo "Retrying forever...";     echo "Retrying forever...";
Line 222: Line 239:
 <code php> <code php>
 try { try {
- throw new RecoverableException("FAILED");+    throw new RecoverableException("FAILED");
 } retry INF (RecoverableException $e, $attempt) { } retry INF (RecoverableException $e, $attempt) {
     echo "Retrying forever...";     echo "Retrying forever...";
Line 232: Line 249:
 === Breaking out of retry === === Breaking out of retry ===
  
-It is sometimes necessary to have some logic that would abort any more retry attempts, like in the case of retrying forever. That can be done using the ``break`` keyword.+It is sometimes necessary to have some logic that would abort any more retry attempts, like in the case of retrying forever. That can be done using the ''break'' keyword.
  
 <code php> <code php>
 try { try {
- throw new RecoverableException("FAILED");+    throw new RecoverableException("FAILED");
 } retry INF (RecoverableException $e, $attempt) { } retry INF (RecoverableException $e, $attempt) {
- if (42 === $e->getCode()) { +    if (42 === $e->getCode()) { 
- break; +        break; 
- }+    }
     echo "Retrying forever...";     echo "Retrying forever...";
 } catch (RecoverableException $e) { } catch (RecoverableException $e) {
Line 258: Line 275:
 $id = 42; $id = 42;
 try { try {
- throw new RecoverableException("FAILED getting ID #{$id}");+    throw new RecoverableException("FAILED getting ID #{$id}");
 } retry 3 (RecoverableException | AnotherRecoverableException $e, $attempt) { } retry 3 (RecoverableException | AnotherRecoverableException $e, $attempt) {
- if (42 === $e->getCode()) { +    if (42 === $e->getCode()) { 
- break; +        break; 
- }+    }
     echo "Failed getting ID #{$id} on try #{$attempt}. Retrying...";     echo "Failed getting ID #{$id} on try #{$attempt}. Retrying...";
- sleep(1);+    sleep(1);
 } catch (RecoverableException | AnotherRecoverableException $e) { } catch (RecoverableException | AnotherRecoverableException $e) {
     echo $e->getMessage();     echo $e->getMessage();
Line 281: Line 298:
 function retry(int $retryCount, callable $tryThis, callable $beforeRetry = null, array $targetExceptions = ['Exception']) function retry(int $retryCount, callable $tryThis, callable $beforeRetry = null, array $targetExceptions = ['Exception'])
 { {
- $attempts = 0;+    $attempts = 0;
     tryCode:     tryCode:
     try {     try {
         return $tryThis();         return $tryThis();
     } catch (\Throwable $e) {     } catch (\Throwable $e) {
-    $isTargetException = false; +        $isTargetException = false; 
-     foreach ($targetExceptions as $targetException) { +        foreach ($targetExceptions as $targetException) { 
-     if ($e instanceof $targetException) { +            if ($e instanceof $targetException) { 
-     $isTargetException = true; +                $isTargetException = true; 
-     break; +                break; 
-     +            
-     }+        }
         if (!$retryCount || !$isTargetException) {         if (!$retryCount || !$isTargetException) {
             throw $e;             throw $e;
Line 299: Line 316:
         $shouldRetry = true;         $shouldRetry = true;
         if ($beforeRetry) {         if ($beforeRetry) {
-        $shouldRetry = $beforeRetry($e, ++$attempts);+            $shouldRetry = $beforeRetry($e, ++$attempts);
         }         }
         if ($shouldRetry) {         if ($shouldRetry) {
-        goto tryCode;+            goto tryCode;
         }         }
         throw $e;         throw $e;
Line 310: Line 327:
 $id = 42; $id = 42;
 try { try {
- $result = retry(3, function () use ($id) { +    $result = retry(3, function () use ($id) { 
-     throw new AnotherRecoverableException("FAILED getting ID #{$id}"); +        throw new AnotherRecoverableException("FAILED getting ID #{$id}"); 
- }, function ($e, $attempt) use ($id) { +    }, function ($e, $attempt) use ($id) { 
- if (42 === $e->getCode()) { +        if (42 === $e->getCode()) { 
- return false; +            return false; 
- +        
-     echo "Failed getting ID #{$id} on try #{$attempt}. Retrying..."; +        echo "Failed getting ID #{$id} on try #{$attempt}. Retrying..."; 
- sleep(1); +        sleep(1); 
- return true; +        return true; 
- }, [RecoverableException::class, AnotherRecoverableException::class]);+    }, [RecoverableException::class, AnotherRecoverableException::class]);
 } catch (RecoverableException | AnotherRecoverableException $e) { } catch (RecoverableException | AnotherRecoverableException $e) {
     echo $e->getMessage();     echo $e->getMessage();
Line 361: Line 378:
 ===== Proposed PHP Version ===== ===== Proposed PHP Version =====
  
-Next PHP 7.2.+Next PHP 7.3.
  
 ===== Proposed Voting Choices ===== ===== Proposed Voting Choices =====
  
 Requires a 2/3 majority. Requires a 2/3 majority.
 +
 +==== Add block-level retry ====
 +
 +<code php>
 +try {
 +    somethingSketchy();
 +} retry 3 (RecoverableException $e, $attempt) {
 +    sleep(1);
 +} catch (RecoverableException $e) {
 +    echo $e->getMessage();
 +}
 +</code>
  
 <doodle title="Add block-level retry?" auth="sammyk" voteType="single" closed="true"> <doodle title="Add block-level retry?" auth="sammyk" voteType="single" closed="true">
Line 371: Line 400:
    * No    * No
 </doodle> </doodle>
 +
 +==== Add retry keyword ====
 +
 +<code php>
 +try {
 +    somethingSketchy();
 +} catch (RecoverableException $e)
 +    retry;
 +}
 +</code>
  
 <doodle title="Add retry keyword?" auth="sammyk" voteType="single" closed="true"> <doodle title="Add retry keyword?" auth="sammyk" voteType="single" closed="true">
Line 379: Line 418:
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
-There are a few WIP implementations of ''retry'':+There are a few slightly outdated WIP implementations of ''retry'':
  
   * [[https://github.com/php/php-src/compare/master...SammyK:retry-block-2017|WIP patch: retry block]]   * [[https://github.com/php/php-src/compare/master...SammyK:retry-block-2017|WIP patch: retry block]]
rfc/retry-keyword.1497878520.txt.gz · Last modified: 2017/09/22 13:28 (external edit)