RetrySettings.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. <?php
  2. /*
  3. * Copyright 2016 Google LLC
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions are
  8. * met:
  9. *
  10. * * Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * * Redistributions in binary form must reproduce the above
  13. * copyright notice, this list of conditions and the following disclaimer
  14. * in the documentation and/or other materials provided with the
  15. * distribution.
  16. * * Neither the name of Google Inc. nor the names of its
  17. * contributors may be used to endorse or promote products derived from
  18. * this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. namespace Google\ApiCore;
  33. /**
  34. * The RetrySettings class is used to configure retrying and timeouts for RPCs.
  35. * This class can be passed as an optional parameter to RPC methods, or as part
  36. * of an optional array in the constructor of a client object. In addition,
  37. * many RPCs and API clients accept a PHP array in place of a RetrySettings
  38. * object. This can be used to change particular retry parameters without
  39. * needing to construct a complete RetrySettings object.
  40. *
  41. * Constructing a RetrySettings object
  42. * -----------------------------------
  43. *
  44. * See the RetrySettings constructor for documentation about parameters that
  45. * can be passed to RetrySettings.
  46. *
  47. * Example of creating a RetrySettings object using the constructor:
  48. * ```
  49. * $retrySettings = new RetrySettings([
  50. * 'initialRetryDelayMillis' => 100,
  51. * 'retryDelayMultiplier' => 1.3,
  52. * 'maxRetryDelayMillis' => 60000,
  53. * 'initialRpcTimeoutMillis' => 20000,
  54. * 'rpcTimeoutMultiplier' => 1.0,
  55. * 'maxRpcTimeoutMillis' => 20000,
  56. * 'totalTimeoutMillis' => 600000,
  57. * 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
  58. * ]);
  59. * ```
  60. *
  61. * It is also possible to create a new RetrySettings object from an existing
  62. * object using the {@see \Google\ApiCore\RetrySettings::with()} method.
  63. *
  64. * Example modifying an existing RetrySettings object using `with()`:
  65. * ```
  66. * $newRetrySettings = $retrySettings->with([
  67. * 'totalTimeoutMillis' => 700000,
  68. * ]);
  69. * ```
  70. *
  71. * Modifying the retry behavior of an RPC method
  72. * ---------------------------------------------
  73. *
  74. * RetrySettings objects can be used to control retries for many RPC methods in
  75. * [google-cloud-php](https://github.com/googleapis/google-cloud-php).
  76. * The examples below make use of the
  77. * [GroupServiceClient](https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/monitoring/v3/groupserviceclient)
  78. * from the [Monitoring V3 API](https://github.com/googleapis/google-cloud-php/tree/master/src/Monitoring/V3),
  79. * but they can be applied to other APIs in the
  80. * [google-cloud-php](https://github.com/googleapis/google-cloud-php) repository.
  81. *
  82. * It is possible to specify the retry behavior to be used by an RPC via the
  83. * `retrySettings` field in the `optionalArgs` parameter. The `retrySettings`
  84. * field can contain either a RetrySettings object, or a PHP array containing
  85. * the particular retry parameters to be updated.
  86. *
  87. * Example of disabling retries for a single call to the
  88. * [listGroups](https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/monitoring/v3/groupserviceclient?method=listGroups)
  89. * method, and setting a custom timeout:
  90. * ```
  91. * $result = $client->listGroups($name, [
  92. * 'retrySettings' => [
  93. * 'retriesEnabled' => false,
  94. * 'noRetriesRpcTimeoutMillis' => 5000,
  95. * ]
  96. * ]);
  97. * ```
  98. *
  99. * Example of creating a new RetrySettings object and using it to override
  100. * the retry settings for a call to the
  101. * [listGroups](https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/monitoring/v3/groupserviceclient?method=listGroups)
  102. * method:
  103. * ```
  104. * $customRetrySettings = new RetrySettings([
  105. * 'initialRetryDelayMillis' => 100,
  106. * 'retryDelayMultiplier' => 1.3,
  107. * 'maxRetryDelayMillis' => 60000,
  108. * 'initialRpcTimeoutMillis' => 20000,
  109. * 'rpcTimeoutMultiplier' => 1.0,
  110. * 'maxRpcTimeoutMillis' => 20000,
  111. * 'totalTimeoutMillis' => 600000,
  112. * 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
  113. * ]);
  114. *
  115. * $result = $client->listGroups($name, [
  116. * 'retrySettings' => $customRetrySettings
  117. * ]);
  118. * ```
  119. *
  120. * Modifying the default retry behavior for RPC methods on a Client object
  121. * -----------------------------------------------------------------------
  122. *
  123. * It is also possible to specify the retry behavior for RPC methods when
  124. * constructing a client object using the 'retrySettingsArray'. The examples
  125. * below again make use of the
  126. * [GroupServiceClient](https://googleapis.github.io/google-cloud-php/#/docs/google-cloud/monitoring/v3/groupserviceclient)
  127. * from the [Monitoring V3 API](https://github.com/googleapis/google-cloud-php/tree/master/src/Monitoring/V3),
  128. * but they can be applied to other APIs in the
  129. * [google-cloud-php](https://github.com/googleapis/google-cloud-php) repository.
  130. *
  131. * The GroupServiceClient object accepts an optional `retrySettingsArray`
  132. * parameter, which can be used to specify retry behavior for RPC methods
  133. * on the client. The `retrySettingsArray` accepts a PHP array in which keys
  134. * are the names of RPC methods on the client, and values are either a
  135. * RetrySettings object or a PHP array containing the particular retry
  136. * parameters to be updated.
  137. *
  138. * Example updating the retry settings for four methods of GroupServiceClient:
  139. * ```
  140. * use Google\Cloud\Monitoring\V3\GroupServiceClient;
  141. *
  142. * $customRetrySettings = new RetrySettings([
  143. * 'initialRetryDelayMillis' => 100,
  144. * 'retryDelayMultiplier' => 1.3,
  145. * 'maxRetryDelayMillis' => 60000,
  146. * 'initialRpcTimeoutMillis' => 20000,
  147. * 'rpcTimeoutMultiplier' => 1.0,
  148. * 'maxRpcTimeoutMillis' => 20000,
  149. * 'totalTimeoutMillis' => 600000,
  150. * 'retryableCodes' => [ApiStatus::DEADLINE_EXCEEDED, ApiStatus::UNAVAILABLE],
  151. * ]);
  152. *
  153. * $updatedCustomRetrySettings = $customRetrySettings->with([
  154. * 'totalTimeoutMillis' => 700000
  155. * ]);
  156. *
  157. * $client = new GroupServiceClient([
  158. * 'retrySettingsArray' => [
  159. * 'listGroups' => ['retriesEnabled' => false],
  160. * 'getGroup' => [
  161. * 'initialRpcTimeoutMillis' => 10000,
  162. * 'maxRpcTimeoutMillis' => 30000,
  163. * 'totalTimeoutMillis' => 60000,
  164. * ],
  165. * 'deleteGroup' => $customRetrySettings,
  166. * 'updateGroup' => $updatedCustomRetrySettings
  167. * ],
  168. * ]);
  169. * ```
  170. *
  171. * Configure the use of logical timeout
  172. * ------------------------------------
  173. *
  174. * To configure the use of a logical timeout, where a logical timeout is the
  175. * duration a method is given to complete one or more RPC attempts, with each
  176. * attempt using only the time remaining in the logical timeout, use
  177. * {@see \Google\ApiCore\RetrySettings::logicalTimeout()} combined with
  178. * {@see \Google\ApiCore\RetrySettings::with()}.
  179. *
  180. * ```
  181. * $timeoutSettings = RetrySettings::logicalTimeout(30000);
  182. *
  183. * $customRetrySettings = $customRetrySettings->with($timeoutSettings);
  184. *
  185. * $result = $client->listGroups($name, [
  186. * 'retrySettings' => $customRetrySettings
  187. * ]);
  188. * ```
  189. *
  190. * {@see \Google\ApiCore\RetrySettings::logicalTimeout()} can also be used on a
  191. * method call independent of a RetrySettings instance.
  192. *
  193. * ```
  194. * $timeoutSettings = RetrySettings::logicalTimeout(30000);
  195. *
  196. * $result = $client->listGroups($name, [
  197. * 'retrySettings' => $timeoutSettings
  198. * ]);
  199. * ```
  200. */
  201. class RetrySettings
  202. {
  203. use ValidationTrait;
  204. private $retriesEnabled;
  205. private $retryableCodes;
  206. private $initialRetryDelayMillis;
  207. private $retryDelayMultiplier;
  208. private $maxRetryDelayMillis;
  209. private $initialRpcTimeoutMillis;
  210. private $rpcTimeoutMultiplier;
  211. private $maxRpcTimeoutMillis;
  212. private $totalTimeoutMillis;
  213. private $noRetriesRpcTimeoutMillis;
  214. /**
  215. * Constructs an instance.
  216. *
  217. * @param array $settings {
  218. * Required. Settings for configuring the retry behavior. All parameters are required except
  219. * $retriesEnabled and $noRetriesRpcTimeoutMillis, which are optional and have defaults
  220. * determined based on the other settings provided.
  221. *
  222. * @type bool $retriesEnabled Optional. Enables retries. If not specified, the value is
  223. * determined using the $retryableCodes setting. If $retryableCodes is empty,
  224. * then $retriesEnabled is set to false; otherwise, it is set to true.
  225. * @type int $noRetriesRpcTimeoutMillis Optional. The timeout of the rpc call to be used
  226. * if $retriesEnabled is false, in milliseconds. It not specified, the value
  227. * of $initialRpcTimeoutMillis is used.
  228. * @type array $retryableCodes The Status codes that are retryable. Each status should be
  229. * either one of the string constants defined on {@see \Google\ApiCore\ApiStatus}
  230. * or an integer constant defined on {@see \Google\Rpc\Code}.
  231. * @type int $initialRetryDelayMillis The initial delay of retry in milliseconds.
  232. * @type int $retryDelayMultiplier The exponential multiplier of retry delay.
  233. * @type int $maxRetryDelayMillis The max delay of retry in milliseconds.
  234. * @type int $initialRpcTimeoutMillis The initial timeout of rpc call in milliseconds.
  235. * @type int $rpcTimeoutMultiplier The exponential multiplier of rpc timeout.
  236. * @type int $maxRpcTimeoutMillis The max timeout of rpc call in milliseconds.
  237. * @type int $totalTimeoutMillis The max accumulative timeout in total.
  238. * }
  239. */
  240. public function __construct(array $settings)
  241. {
  242. $this->validateNotNull($settings, [
  243. 'initialRetryDelayMillis',
  244. 'retryDelayMultiplier',
  245. 'maxRetryDelayMillis',
  246. 'initialRpcTimeoutMillis',
  247. 'rpcTimeoutMultiplier',
  248. 'maxRpcTimeoutMillis',
  249. 'totalTimeoutMillis',
  250. 'retryableCodes'
  251. ]);
  252. $this->initialRetryDelayMillis = $settings['initialRetryDelayMillis'];
  253. $this->retryDelayMultiplier = $settings['retryDelayMultiplier'];
  254. $this->maxRetryDelayMillis = $settings['maxRetryDelayMillis'];
  255. $this->initialRpcTimeoutMillis = $settings['initialRpcTimeoutMillis'];
  256. $this->rpcTimeoutMultiplier = $settings['rpcTimeoutMultiplier'];
  257. $this->maxRpcTimeoutMillis = $settings['maxRpcTimeoutMillis'];
  258. $this->totalTimeoutMillis = $settings['totalTimeoutMillis'];
  259. $this->retryableCodes = $settings['retryableCodes'];
  260. $this->retriesEnabled = array_key_exists('retriesEnabled', $settings)
  261. ? $settings['retriesEnabled']
  262. : (count($this->retryableCodes) > 0);
  263. $this->noRetriesRpcTimeoutMillis = array_key_exists('noRetriesRpcTimeoutMillis', $settings)
  264. ? $settings['noRetriesRpcTimeoutMillis']
  265. : $this->initialRpcTimeoutMillis;
  266. }
  267. /**
  268. * Constructs an array mapping method names to CallSettings.
  269. *
  270. * @param string $serviceName
  271. * The fully-qualified name of this service, used as a key into
  272. * the client config file.
  273. * @param array $clientConfig
  274. * An array parsed from the standard API client config file.
  275. * @param bool $disableRetries
  276. * Disable retries in all loaded RetrySettings objects. Defaults to false.
  277. * @throws ValidationException
  278. * @return RetrySettings[] $retrySettings
  279. */
  280. public static function load(
  281. string $serviceName,
  282. array $clientConfig,
  283. bool $disableRetries = false
  284. ) {
  285. $serviceRetrySettings = [];
  286. $serviceConfig = $clientConfig['interfaces'][$serviceName];
  287. $retryCodes = $serviceConfig['retry_codes'];
  288. $retryParams = $serviceConfig['retry_params'];
  289. foreach ($serviceConfig['methods'] as $methodName => $methodConfig) {
  290. $timeoutMillis = $methodConfig['timeout_millis'];
  291. if (empty($methodConfig['retry_codes_name']) || empty($methodConfig['retry_params_name'])) {
  292. // Construct a RetrySettings object with retries disabled
  293. $retrySettings = self::constructDefault()->with([
  294. 'noRetriesRpcTimeoutMillis' => $timeoutMillis,
  295. ]);
  296. } else {
  297. $retryCodesName = $methodConfig['retry_codes_name'];
  298. $retryParamsName = $methodConfig['retry_params_name'];
  299. if (!array_key_exists($retryCodesName, $retryCodes)) {
  300. throw new ValidationException("Invalid retry_codes_name setting: '$retryCodesName'");
  301. }
  302. if (!array_key_exists($retryParamsName, $retryParams)) {
  303. throw new ValidationException("Invalid retry_params_name setting: '$retryParamsName'");
  304. }
  305. foreach ($retryCodes[$retryCodesName] as $status) {
  306. if (!ApiStatus::isValidStatus($status)) {
  307. throw new ValidationException("Invalid status code: '$status'");
  308. }
  309. }
  310. $retryParameters = self::convertArrayFromSnakeCase($retryParams[$retryParamsName]) + [
  311. 'retryableCodes' => $retryCodes[$retryCodesName],
  312. 'noRetriesRpcTimeoutMillis' => $timeoutMillis,
  313. ];
  314. if ($disableRetries) {
  315. $retryParameters['retriesEnabled'] = false;
  316. }
  317. $retrySettings = new RetrySettings($retryParameters);
  318. }
  319. $serviceRetrySettings[$methodName] = $retrySettings;
  320. }
  321. return $serviceRetrySettings;
  322. }
  323. public static function constructDefault()
  324. {
  325. return new RetrySettings([
  326. 'retriesEnabled' => false,
  327. 'noRetriesRpcTimeoutMillis' => 30000,
  328. 'initialRetryDelayMillis' => 100,
  329. 'retryDelayMultiplier' => 1.3,
  330. 'maxRetryDelayMillis' => 60000,
  331. 'initialRpcTimeoutMillis' => 20000,
  332. 'rpcTimeoutMultiplier' => 1,
  333. 'maxRpcTimeoutMillis' => 20000,
  334. 'totalTimeoutMillis' => 600000,
  335. 'retryableCodes' => []]);
  336. }
  337. /**
  338. * Creates a new instance of RetrySettings that updates the settings in the existing instance
  339. * with the settings specified in the $settings parameter.
  340. *
  341. * @param array $settings {
  342. * Settings for configuring the retry behavior. Supports all of the options supported by
  343. * the constructor; see {@see \Google\ApiCore\RetrySettings::__construct()}. All parameters
  344. * are optional - all unset parameters will default to the value in the existing instance.
  345. * }
  346. * @return RetrySettings
  347. */
  348. public function with(array $settings)
  349. {
  350. $existingSettings = [
  351. 'initialRetryDelayMillis' => $this->getInitialRetryDelayMillis(),
  352. 'retryDelayMultiplier' => $this->getRetryDelayMultiplier(),
  353. 'maxRetryDelayMillis' => $this->getMaxRetryDelayMillis(),
  354. 'initialRpcTimeoutMillis' => $this->getInitialRpcTimeoutMillis(),
  355. 'rpcTimeoutMultiplier' => $this->getRpcTimeoutMultiplier(),
  356. 'maxRpcTimeoutMillis' => $this->getMaxRpcTimeoutMillis(),
  357. 'totalTimeoutMillis' => $this->getTotalTimeoutMillis(),
  358. 'retryableCodes' => $this->getRetryableCodes(),
  359. 'retriesEnabled' => $this->retriesEnabled(),
  360. 'noRetriesRpcTimeoutMillis' => $this->getNoRetriesRpcTimeoutMillis(),
  361. ];
  362. return new RetrySettings($settings + $existingSettings);
  363. }
  364. /**
  365. * Creates an associative array of the {@see \Google\ApiCore\RetrySettings} timeout fields configured
  366. * with the given timeout specified in the $timeout parameter interpreted as a logical timeout.
  367. *
  368. * @param int $timeout The timeout in milliseconds to be used as a logical call timeout.
  369. * @return array
  370. */
  371. public static function logicalTimeout(int $timeout)
  372. {
  373. return [
  374. 'initialRpcTimeoutMillis' => $timeout,
  375. 'maxRpcTimeoutMillis' => $timeout,
  376. 'totalTimeoutMillis' => $timeout,
  377. 'noRetriesRpcTimeoutMillis' => $timeout,
  378. 'rpcTimeoutMultiplier' => 1.0
  379. ];
  380. }
  381. /**
  382. * @return bool Returns true if retries are enabled, otherwise returns false.
  383. */
  384. public function retriesEnabled()
  385. {
  386. return $this->retriesEnabled;
  387. }
  388. /**
  389. * @return int The timeout of the rpc call to be used if $retriesEnabled is false,
  390. * in milliseconds.
  391. */
  392. public function getNoRetriesRpcTimeoutMillis()
  393. {
  394. return $this->noRetriesRpcTimeoutMillis;
  395. }
  396. /**
  397. * @return int[] Status codes to retry
  398. */
  399. public function getRetryableCodes()
  400. {
  401. return $this->retryableCodes;
  402. }
  403. /**
  404. * @return int The initial retry delay in milliseconds. If $this->retriesEnabled()
  405. * is false, this setting is unused.
  406. */
  407. public function getInitialRetryDelayMillis()
  408. {
  409. return $this->initialRetryDelayMillis;
  410. }
  411. /**
  412. * @return float The retry delay multiplier. If $this->retriesEnabled()
  413. * is false, this setting is unused.
  414. */
  415. public function getRetryDelayMultiplier()
  416. {
  417. return $this->retryDelayMultiplier;
  418. }
  419. /**
  420. * @return int The maximum retry delay in milliseconds. If $this->retriesEnabled()
  421. * is false, this setting is unused.
  422. */
  423. public function getMaxRetryDelayMillis()
  424. {
  425. return $this->maxRetryDelayMillis;
  426. }
  427. /**
  428. * @return int The initial rpc timeout in milliseconds. If $this->retriesEnabled()
  429. * is false, this setting is unused - use noRetriesRpcTimeoutMillis to
  430. * set the timeout in that case.
  431. */
  432. public function getInitialRpcTimeoutMillis()
  433. {
  434. return $this->initialRpcTimeoutMillis;
  435. }
  436. /**
  437. * @return float The rpc timeout multiplier. If $this->retriesEnabled()
  438. * is false, this setting is unused.
  439. */
  440. public function getRpcTimeoutMultiplier()
  441. {
  442. return $this->rpcTimeoutMultiplier;
  443. }
  444. /**
  445. * @return int The maximum rpc timeout in milliseconds. If $this->retriesEnabled()
  446. * is false, this setting is unused - use noRetriesRpcTimeoutMillis to
  447. * set the timeout in that case.
  448. */
  449. public function getMaxRpcTimeoutMillis()
  450. {
  451. return $this->maxRpcTimeoutMillis;
  452. }
  453. /**
  454. * @return int The total time in milliseconds to spend on the call, including all
  455. * retry attempts and delays between attempts. If $this->retriesEnabled()
  456. * is false, this setting is unused - use noRetriesRpcTimeoutMillis to
  457. * set the timeout in that case.
  458. */
  459. public function getTotalTimeoutMillis()
  460. {
  461. return $this->totalTimeoutMillis;
  462. }
  463. private static function convertArrayFromSnakeCase(array $settings)
  464. {
  465. $camelCaseSettings = [];
  466. foreach ($settings as $key => $value) {
  467. $camelCaseKey = str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
  468. $camelCaseSettings[lcfirst($camelCaseKey)] = $value;
  469. }
  470. return $camelCaseSettings;
  471. }
  472. }