files.com 1.1.813 → 1.1.814

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (461) hide show
  1. checksums.yaml +4 -4
  2. data/_VERSION +1 -1
  3. data/doc/Files/APIConnectionError.html +1 -1
  4. data/doc/Files/APIError.html +1 -1
  5. data/doc/Files/AccountAlreadyExistsError.html +1 -1
  6. data/doc/Files/AccountLineItem.html +1 -1
  7. data/doc/Files/AccountOverdueError.html +1 -1
  8. data/doc/Files/Action.html +1 -1
  9. data/doc/Files/ActionLog.html +1 -1
  10. data/doc/Files/ActionNotificationExport.html +1 -1
  11. data/doc/Files/ActionNotificationExportResult.html +1 -1
  12. data/doc/Files/AdditionalAuthenticationRequiredError.html +1 -1
  13. data/doc/Files/AgentNode.html +1 -1
  14. data/doc/Files/AgentNodeConnection.html +1 -1
  15. data/doc/Files/AgentNodeInstance.html +1 -1
  16. data/doc/Files/AgentPushUpdate.html +1 -1
  17. data/doc/Files/AgentPushUpdateBlockedError.html +1 -1
  18. data/doc/Files/AgentUnavailableError.html +1 -1
  19. data/doc/Files/AgentUpgradeRequiredError.html +1 -1
  20. data/doc/Files/AiAssistantPersonality.html +1 -1
  21. data/doc/Files/AiTask.html +1 -1
  22. data/doc/Files/AiTaskCannotBeRunManuallyError.html +1 -1
  23. data/doc/Files/AlreadyCompletedError.html +1 -1
  24. data/doc/Files/Api.html +20 -13
  25. data/doc/Files/ApiClient/RequestLogContext.html +29 -29
  26. data/doc/Files/ApiClient/TransferSettings.html +721 -0
  27. data/doc/Files/ApiClient.html +756 -204
  28. data/doc/Files/ApiKey.html +1 -1
  29. data/doc/Files/ApiKeyIsDisabledError.html +1 -1
  30. data/doc/Files/ApiKeyIsPathRestrictedError.html +1 -1
  31. data/doc/Files/ApiKeyNotFoundError.html +1 -1
  32. data/doc/Files/ApiKeyOnlyForDesktopAppError.html +1 -1
  33. data/doc/Files/ApiKeyOnlyForFileOperationsError.html +1 -1
  34. data/doc/Files/ApiKeyOnlyForMobileAppError.html +1 -1
  35. data/doc/Files/ApiKeyOnlyForOfficeIntegrationError.html +1 -1
  36. data/doc/Files/ApiKeySessionsNotSupportedError.html +1 -1
  37. data/doc/Files/ApiRequestLog.html +1 -1
  38. data/doc/Files/App.html +1 -1
  39. data/doc/Files/As2IncomingMessage.html +1 -1
  40. data/doc/Files/As2OutgoingMessage.html +1 -1
  41. data/doc/Files/As2Partner.html +1 -1
  42. data/doc/Files/As2Station.html +1 -1
  43. data/doc/Files/AttachmentTooLargeError.html +1 -1
  44. data/doc/Files/AuthenticationError.html +1 -1
  45. data/doc/Files/AuthenticationRequiredError.html +1 -1
  46. data/doc/Files/Auto.html +1 -1
  47. data/doc/Files/Automation.html +1 -1
  48. data/doc/Files/AutomationAuthoringSchema.html +1 -1
  49. data/doc/Files/AutomationCannotBeRunManuallyError.html +1 -1
  50. data/doc/Files/AutomationExecutionNode.html +1 -1
  51. data/doc/Files/AutomationLog.html +1 -1
  52. data/doc/Files/AutomationRun.html +1 -1
  53. data/doc/Files/AutomationsUnavailableError.html +1 -1
  54. data/doc/Files/BadRequestError.html +1 -1
  55. data/doc/Files/BandwidthSnapshot.html +1 -1
  56. data/doc/Files/Behavior.html +1 -1
  57. data/doc/Files/BehaviorNotAllowedOnRemoteServerError.html +1 -1
  58. data/doc/Files/BillingInformationHiddenError.html +1 -1
  59. data/doc/Files/BillingPermissionRequiredError.html +1 -1
  60. data/doc/Files/BufferedUploadDisabledForThisDestinationError.html +1 -1
  61. data/doc/Files/Bundle.html +1 -1
  62. data/doc/Files/BundleAction.html +1 -1
  63. data/doc/Files/BundleDownload.html +1 -1
  64. data/doc/Files/BundleMaximumUsesReachedError.html +1 -1
  65. data/doc/Files/BundleNotification.html +1 -1
  66. data/doc/Files/BundleOnlyAllowsPreviewsError.html +1 -1
  67. data/doc/Files/BundleOperationRequiresSubfolderError.html +1 -1
  68. data/doc/Files/BundlePath.html +1 -1
  69. data/doc/Files/BundlePathNotFoundError.html +1 -1
  70. data/doc/Files/BundlePermissionRequiredError.html +1 -1
  71. data/doc/Files/BundleRecipient.html +1 -1
  72. data/doc/Files/BundleRegistration.html +1 -1
  73. data/doc/Files/BundleRegistrationCodeFailedError.html +1 -1
  74. data/doc/Files/BundleRegistrationNotFoundError.html +1 -1
  75. data/doc/Files/CannotAdministerHigherLevelUserError.html +1 -1
  76. data/doc/Files/CannotDownloadDirectoryError.html +1 -1
  77. data/doc/Files/CannotLoginWhileUsingKeyError.html +1 -1
  78. data/doc/Files/CantActForOtherUserError.html +1 -1
  79. data/doc/Files/CantMoveWithMultipleLocationsError.html +1 -1
  80. data/doc/Files/ChatMessage.html +1 -1
  81. data/doc/Files/ChatSession.html +1 -1
  82. data/doc/Files/ChildSiteManagementPolicy.html +1 -1
  83. data/doc/Files/Clickwrap.html +1 -1
  84. data/doc/Files/CodeNotFoundError.html +1 -1
  85. data/doc/Files/ConfigurationLockedPathError.html +1 -1
  86. data/doc/Files/ContactAdminForPasswordChangeHelpError.html +1 -1
  87. data/doc/Files/CouldNotCreateParentError.html +1 -1
  88. data/doc/Files/CustomDomain.html +1 -1
  89. data/doc/Files/DatetimeParseError.html +1 -1
  90. data/doc/Files/DesktopConfigurationProfile.html +1 -1
  91. data/doc/Files/DestinationExistsError.html +1 -1
  92. data/doc/Files/DestinationFolderLimitedError.html +1 -1
  93. data/doc/Files/DestinationParentConflictError.html +1 -1
  94. data/doc/Files/DestinationParentDoesNotExistError.html +1 -1
  95. data/doc/Files/DestinationSameError.html +1 -1
  96. data/doc/Files/DestinationSiteMismatchError.html +1 -1
  97. data/doc/Files/DirectConnectionInfo.html +1 -1
  98. data/doc/Files/DnsRecord.html +1 -1
  99. data/doc/Files/DoesNotSupportSortingError.html +1 -1
  100. data/doc/Files/DuplicateShareRecipientError.html +1 -1
  101. data/doc/Files/EmailIncomingMessage.html +1 -1
  102. data/doc/Files/EmailLog.html +1 -1
  103. data/doc/Files/Error.html +2 -2
  104. data/doc/Files/Errors.html +1 -1
  105. data/doc/Files/EventChannel.html +1 -1
  106. data/doc/Files/EventDeliveryAttempt.html +1 -1
  107. data/doc/Files/EventRecord.html +1 -1
  108. data/doc/Files/EventSubscription.html +1 -1
  109. data/doc/Files/EventTarget.html +1 -1
  110. data/doc/Files/ExavaultApiRequestLog.html +1 -1
  111. data/doc/Files/ExceededRuntimeLimitError.html +1 -1
  112. data/doc/Files/Expectation.html +1 -1
  113. data/doc/Files/ExpectationAlreadyHasOpenWindowError.html +1 -1
  114. data/doc/Files/ExpectationEvaluation.html +1 -1
  115. data/doc/Files/ExpectationIncident.html +1 -1
  116. data/doc/Files/ExpectationNotManualTriggerError.html +1 -1
  117. data/doc/Files/ExpiredPrivateKeyError.html +1 -1
  118. data/doc/Files/ExpiredPublicKeyError.html +1 -1
  119. data/doc/Files/ExportFailureError.html +1 -1
  120. data/doc/Files/ExportNotReadyError.html +1 -1
  121. data/doc/Files/ExternalEvent.html +1 -1
  122. data/doc/Files/FailedToChangePasswordError.html +1 -1
  123. data/doc/Files/File.html +2019 -1515
  124. data/doc/Files/FileAction.html +1 -1
  125. data/doc/Files/FileActionError.html +1 -1
  126. data/doc/Files/FileComment.html +1 -1
  127. data/doc/Files/FileCommentReaction.html +1 -1
  128. data/doc/Files/FileLockedError.html +1 -1
  129. data/doc/Files/FileMigration.html +1 -1
  130. data/doc/Files/FileMigrationLog.html +1 -1
  131. data/doc/Files/FileNotFoundError.html +1 -1
  132. data/doc/Files/FileNotUploadedError.html +1 -1
  133. data/doc/Files/FilePendingProcessingError.html +1 -1
  134. data/doc/Files/FileProcessingErrorError.html +1 -1
  135. data/doc/Files/FileTooBigToDecryptError.html +1 -1
  136. data/doc/Files/FileTooBigToEncryptError.html +1 -1
  137. data/doc/Files/FileUploadNotFoundError.html +1 -1
  138. data/doc/Files/FileUploadPart.html +1 -1
  139. data/doc/Files/FileUploadedToWrongRegionError.html +1 -1
  140. data/doc/Files/FileUtils.html +1 -1
  141. data/doc/Files/FilenameTooLongError.html +1 -1
  142. data/doc/Files/FilesAgentFailedAuthorizationError.html +1 -1
  143. data/doc/Files/FilesNativeStorageOverloadedError.html +1 -1
  144. data/doc/Files/Folder.html +1 -1
  145. data/doc/Files/FolderAdminOrBillingPermissionRequiredError.html +1 -1
  146. data/doc/Files/FolderAdminPermissionRequiredError.html +1 -1
  147. data/doc/Files/FolderLockedError.html +1 -1
  148. data/doc/Files/FolderMustNotBeAFileError.html +1 -1
  149. data/doc/Files/FolderNotEmptyError.html +1 -1
  150. data/doc/Files/FoldersNotAllowedError.html +1 -1
  151. data/doc/Files/FormField.html +1 -1
  152. data/doc/Files/FormFieldSet.html +1 -1
  153. data/doc/Files/FtpActionLog.html +1 -1
  154. data/doc/Files/FullPermissionRequiredError.html +1 -1
  155. data/doc/Files/GpgKey.html +1 -1
  156. data/doc/Files/Group.html +1 -1
  157. data/doc/Files/GroupNotFoundError.html +1 -1
  158. data/doc/Files/GroupUser.html +1 -1
  159. data/doc/Files/History.html +1 -1
  160. data/doc/Files/HistoryExport.html +1 -1
  161. data/doc/Files/HistoryExportResult.html +1 -1
  162. data/doc/Files/HistoryPermissionRequiredError.html +1 -1
  163. data/doc/Files/HistoryUnavailableError.html +1 -1
  164. data/doc/Files/HolidayCalendar.html +1 -1
  165. data/doc/Files/HolidayRegion.html +1 -1
  166. data/doc/Files/Image.html +1 -1
  167. data/doc/Files/InAppAiAssistantUnavailableError.html +1 -1
  168. data/doc/Files/InboundS3Log.html +1 -1
  169. data/doc/Files/InboxNotFoundError.html +1 -1
  170. data/doc/Files/InboxRecipient.html +1 -1
  171. data/doc/Files/InboxRegistration.html +1 -1
  172. data/doc/Files/InboxRegistrationCodeFailedError.html +1 -1
  173. data/doc/Files/InboxUpload.html +1 -1
  174. data/doc/Files/InsufficientPermissionForParamsError.html +1 -1
  175. data/doc/Files/InsufficientPermissionForSiteError.html +1 -1
  176. data/doc/Files/IntegrationCentricProfile.html +1 -1
  177. data/doc/Files/InternalGeneralErrorError.html +1 -1
  178. data/doc/Files/InvalidBodyError.html +1 -1
  179. data/doc/Files/InvalidBundleCodeError.html +1 -1
  180. data/doc/Files/InvalidCredentialsError.html +1 -1
  181. data/doc/Files/InvalidCursorError.html +1 -1
  182. data/doc/Files/InvalidCursorTypeForSortError.html +1 -1
  183. data/doc/Files/InvalidEtagsError.html +1 -1
  184. data/doc/Files/InvalidFileTypeError.html +1 -1
  185. data/doc/Files/InvalidFilenameError.html +1 -1
  186. data/doc/Files/InvalidFilterAliasCombinationError.html +1 -1
  187. data/doc/Files/InvalidFilterFieldError.html +1 -1
  188. data/doc/Files/InvalidFilterParamError.html +1 -1
  189. data/doc/Files/InvalidFilterParamFormatError.html +1 -1
  190. data/doc/Files/InvalidFilterParamValueError.html +1 -1
  191. data/doc/Files/InvalidInputEncodingError.html +1 -1
  192. data/doc/Files/InvalidInterfaceError.html +1 -1
  193. data/doc/Files/InvalidOauthError.html +1 -1
  194. data/doc/Files/InvalidOauthProviderError.html +1 -1
  195. data/doc/Files/InvalidOrExpiredCodeError.html +1 -1
  196. data/doc/Files/InvalidParameterError.html +5 -1
  197. data/doc/Files/InvalidPathError.html +1 -1
  198. data/doc/Files/InvalidPriorityColorError.html +1 -1
  199. data/doc/Files/InvalidRangeError.html +1 -1
  200. data/doc/Files/InvalidReturnToUrlError.html +1 -1
  201. data/doc/Files/InvalidSearchQueryError.html +1 -1
  202. data/doc/Files/InvalidSessionError.html +1 -1
  203. data/doc/Files/InvalidSiteError.html +1 -1
  204. data/doc/Files/InvalidSortFieldError.html +1 -1
  205. data/doc/Files/InvalidSortFilterCombinationError.html +1 -1
  206. data/doc/Files/InvalidUploadOffsetError.html +1 -1
  207. data/doc/Files/InvalidUploadPartGapError.html +1 -1
  208. data/doc/Files/InvalidUploadPartSizeError.html +1 -1
  209. data/doc/Files/InvalidUsernameOrPasswordError.html +1 -1
  210. data/doc/Files/InvalidWorkspaceIdHeaderError.html +1 -1
  211. data/doc/Files/InvalidZipFileError.html +1 -1
  212. data/doc/Files/Invoice.html +1 -1
  213. data/doc/Files/InvoiceLineItem.html +1 -1
  214. data/doc/Files/IpAddress.html +1 -1
  215. data/doc/Files/KeyLifecycleRule.html +1 -1
  216. data/doc/Files/List.html +1 -1
  217. data/doc/Files/Lock.html +1 -1
  218. data/doc/Files/LockOperationBusyError.html +1 -1
  219. data/doc/Files/LockedOutError.html +1 -1
  220. data/doc/Files/LockoutRegionMismatchError.html +1 -1
  221. data/doc/Files/MetadataCategory.html +1 -1
  222. data/doc/Files/MetadataNotSupportedOnRemotesError.html +1 -1
  223. data/doc/Files/MethodNotAllowedError.html +1 -1
  224. data/doc/Files/MigrationInProgressError.html +1 -1
  225. data/doc/Files/MissingParameterError.html +1 -1
  226. data/doc/Files/ModelSaveErrorError.html +1 -1
  227. data/doc/Files/MoverAccessDeniedError.html +1 -1
  228. data/doc/Files/MoverPackageRequiredError.html +1 -1
  229. data/doc/Files/MultipleProcessingErrorsError.html +1 -1
  230. data/doc/Files/MultipleSortParamsNotAllowedError.html +1 -1
  231. data/doc/Files/MustAuthenticateWithApiKeyError.html +1 -1
  232. data/doc/Files/NeedAdminPermissionForInboxError.html +1 -1
  233. data/doc/Files/NestedNotFoundError.html +1 -1
  234. data/doc/Files/NoAccountForSiteError.html +1 -1
  235. data/doc/Files/NoValidInputParamsError.html +1 -1
  236. data/doc/Files/NonAdminsMustQueryByFolderOrPathError.html +1 -1
  237. data/doc/Files/NotAllowedToCreateBundleError.html +1 -1
  238. data/doc/Files/NotAuthenticatedError.html +1 -1
  239. data/doc/Files/NotAuthorizedError.html +1 -1
  240. data/doc/Files/NotEnqueuableSyncError.html +1 -1
  241. data/doc/Files/NotFoundError.html +1 -1
  242. data/doc/Files/NotImplementedError.html +1 -1
  243. data/doc/Files/Notification.html +1 -1
  244. data/doc/Files/OffsetUploadNotAllowedWithMalwareScanningError.html +1 -1
  245. data/doc/Files/OneTimePasswordIncorrectError.html +1 -1
  246. data/doc/Files/OutboundConnectionLog.html +1 -1
  247. data/doc/Files/PartNumberTooLargeError.html +1 -1
  248. data/doc/Files/Partner.html +1 -1
  249. data/doc/Files/PartnerChannel.html +1 -1
  250. data/doc/Files/PartnerChannelTemplate.html +1 -1
  251. data/doc/Files/PartnerConnection.html +1 -1
  252. data/doc/Files/PartnerSite.html +1 -1
  253. data/doc/Files/PartnerSiteRequest.html +1 -1
  254. data/doc/Files/PasswordChangeNotRequiredError.html +1 -1
  255. data/doc/Files/PasswordChangeRequiredError.html +1 -1
  256. data/doc/Files/PathCannotHaveTrailingWhitespaceError.html +1 -1
  257. data/doc/Files/PathTooLongError.html +1 -1
  258. data/doc/Files/PathUtil.html +1 -1
  259. data/doc/Files/Payment.html +1 -1
  260. data/doc/Files/PaymentLineItem.html +1 -1
  261. data/doc/Files/PaymentMethodErrorError.html +1 -1
  262. data/doc/Files/PendingWorkEvent.html +1 -1
  263. data/doc/Files/Permission.html +1 -1
  264. data/doc/Files/PlanNotFoundError.html +1 -1
  265. data/doc/Files/Preview.html +1 -1
  266. data/doc/Files/PreviewOnlyPermissionCannotDownloadError.html +1 -1
  267. data/doc/Files/ProcessingFailureError.html +1 -1
  268. data/doc/Files/PublicHostingRequestLog.html +1 -1
  269. data/doc/Files/PublicIpAddress.html +1 -1
  270. data/doc/Files/PublicKey.html +1 -1
  271. data/doc/Files/RateLimitedError.html +1 -1
  272. data/doc/Files/ReadOnlySessionError.html +1 -1
  273. data/doc/Files/ReadPermissionRequiredError.html +1 -1
  274. data/doc/Files/ReauthenticationFailedError.html +1 -1
  275. data/doc/Files/ReauthenticationFailedFinalError.html +1 -1
  276. data/doc/Files/ReauthenticationNeededActionError.html +1 -1
  277. data/doc/Files/ReauthenticationNeededFieldsError.html +1 -1
  278. data/doc/Files/ReauthenticationRateLimitedError.html +1 -1
  279. data/doc/Files/RecaptchaFailedError.html +1 -1
  280. data/doc/Files/RecipientAlreadySharedError.html +1 -1
  281. data/doc/Files/RemoteBandwidthSnapshot.html +1 -1
  282. data/doc/Files/RemoteDesktopDebugLoggingDisabledError.html +1 -1
  283. data/doc/Files/RemoteEntryReadOnlyError.html +1 -1
  284. data/doc/Files/RemoteMountBackend.html +1 -1
  285. data/doc/Files/RemoteServer.html +1 -1
  286. data/doc/Files/RemoteServerConfigurationFile.html +1 -1
  287. data/doc/Files/RemoteServerCredential.html +1 -1
  288. data/doc/Files/RemoteServerErrorError.html +1 -1
  289. data/doc/Files/Request.html +1 -1
  290. data/doc/Files/RequestBodyTooLargeError.html +1 -1
  291. data/doc/Files/RequestParamsContainInvalidCharacterError.html +1 -1
  292. data/doc/Files/RequestParamsInvalidError.html +1 -1
  293. data/doc/Files/RequestParamsRequiredError.html +1 -1
  294. data/doc/Files/ResourceBelongsToParentSiteError.html +1 -1
  295. data/doc/Files/ResourceLockedError.html +1 -1
  296. data/doc/Files/Response.html +1 -1
  297. data/doc/Files/Restore.html +1 -1
  298. data/doc/Files/RootFolderBehaviorSiteAdminRequiredError.html +1 -1
  299. data/doc/Files/RootFolderBehaviorSkipSiteAdminRequiredError.html +1 -1
  300. data/doc/Files/Schedule.html +1 -1
  301. data/doc/Files/ScheduledExport.html +1 -1
  302. data/doc/Files/ScimLog.html +1 -1
  303. data/doc/Files/SearchAllOnChildPathError.html +1 -1
  304. data/doc/Files/SearchUnavailableError.html +1 -1
  305. data/doc/Files/Secret.html +1 -1
  306. data/doc/Files/SelfManagedRequiredError.html +1 -1
  307. data/doc/Files/ServiceUnavailableError.html +1 -1
  308. data/doc/Files/Session.html +1 -1
  309. data/doc/Files/SettingsChange.html +1 -1
  310. data/doc/Files/SftpActionLog.html +1 -1
  311. data/doc/Files/SftpHostKey.html +1 -1
  312. data/doc/Files/ShareGroup.html +1 -1
  313. data/doc/Files/ShareGroupMember.html +1 -1
  314. data/doc/Files/SiemHttpDestination.html +1 -1
  315. data/doc/Files/SiemHttpDestinationEvent.html +1 -1
  316. data/doc/Files/Site.html +1 -1
  317. data/doc/Files/SiteAdminOrPartnerAdminPermissionRequiredError.html +1 -1
  318. data/doc/Files/SiteAdminOrWorkspaceAdminOrFolderAdminPermissionRequiredError.html +1 -1
  319. data/doc/Files/SiteAdminOrWorkspaceAdminOrPartnerAdminOrFolderAdminPermissionRequiredError.html +1 -1
  320. data/doc/Files/SiteAdminOrWorkspaceAdminOrPartnerAdminPermissionRequiredError.html +1 -1
  321. data/doc/Files/SiteAdminOrWorkspaceAdminPermissionRequiredError.html +1 -1
  322. data/doc/Files/SiteAdminRequiredError.html +1 -1
  323. data/doc/Files/SiteConfigurationError.html +1 -1
  324. data/doc/Files/SiteDisabledError.html +1 -1
  325. data/doc/Files/SiteFilesAreImmutableError.html +1 -1
  326. data/doc/Files/SiteNotFoundError.html +1 -1
  327. data/doc/Files/SiteSubdomainRedirect.html +1 -1
  328. data/doc/Files/SiteWasRemovedError.html +1 -1
  329. data/doc/Files/SizableIO.html +1 -1
  330. data/doc/Files/Snapshot.html +1 -1
  331. data/doc/Files/SsoEvent.html +1 -1
  332. data/doc/Files/SsoStrategy.html +1 -1
  333. data/doc/Files/Status.html +1 -1
  334. data/doc/Files/Style.html +1 -1
  335. data/doc/Files/SubfolderLockedError.html +1 -1
  336. data/doc/Files/Sync.html +1 -1
  337. data/doc/Files/SyncInProgressError.html +1 -1
  338. data/doc/Files/SyncLog.html +1 -1
  339. data/doc/Files/SyncRun.html +1 -1
  340. data/doc/Files/SyncRunLiveTransfer.html +1 -1
  341. data/doc/Files/SystemProfiler.html +1 -1
  342. data/doc/Files/TooManyConcurrentLoginsError.html +1 -1
  343. data/doc/Files/TooManyConcurrentRequestsError.html +1 -1
  344. data/doc/Files/TooManyLoginAttemptsError.html +1 -1
  345. data/doc/Files/TooManyRequestsError.html +1 -1
  346. data/doc/Files/TooManySharesError.html +1 -1
  347. data/doc/Files/Transfer/BufferLimitError.html +175 -0
  348. data/doc/Files/Transfer/ByteBudget/Reservation.html +301 -0
  349. data/doc/Files/Transfer/ByteBudget.html +748 -0
  350. data/doc/Files/Transfer/CancelledError.html +235 -0
  351. data/doc/Files/Transfer/Checkpoint.html +1371 -0
  352. data/doc/Files/Transfer/CheckpointError.html +173 -0
  353. data/doc/Files/Transfer/ClientTransport/ClientResponse.html +661 -0
  354. data/doc/Files/Transfer/ClientTransport/Reader.html +530 -0
  355. data/doc/Files/Transfer/ClientTransport.html +675 -0
  356. data/doc/Files/Transfer/Clock.html +189 -0
  357. data/doc/Files/Transfer/Control.html +667 -0
  358. data/doc/Files/Transfer/Controller/Attempt.html +465 -0
  359. data/doc/Files/Transfer/Controller.html +1166 -0
  360. data/doc/Files/Transfer/Coordinator/Timing.html +568 -0
  361. data/doc/Files/Transfer/Coordinator.html +534 -0
  362. data/doc/Files/Transfer/DownloadCoordinator/FileSink.html +976 -0
  363. data/doc/Files/Transfer/DownloadCoordinator/IoSink.html +1168 -0
  364. data/doc/Files/Transfer/DownloadCoordinator/Piece.html +875 -0
  365. data/doc/Files/Transfer/DownloadCoordinator.html +728 -0
  366. data/doc/Files/Transfer/FileSlots.html +480 -0
  367. data/doc/Files/Transfer/FrontendResponseError.html +235 -0
  368. data/doc/Files/Transfer/Holds.html +505 -0
  369. data/doc/Files/Transfer/HttpTransport.html +579 -0
  370. data/doc/Files/Transfer/Job.html +2338 -0
  371. data/doc/Files/Transfer/Options.html +924 -0
  372. data/doc/Files/Transfer/PausedError.html +319 -0
  373. data/doc/Files/Transfer/Policy.html +1642 -0
  374. data/doc/Files/Transfer/RequestPermits.html +160 -0
  375. data/doc/Files/Transfer/SourceChangedError.html +235 -0
  376. data/doc/Files/Transfer/StorageResponse.html +546 -0
  377. data/doc/Files/Transfer/StorageRetry.html +1098 -0
  378. data/doc/Files/Transfer/UploadCoordinator/Grant.html +1048 -0
  379. data/doc/Files/Transfer/UploadCoordinator/Part.html +1048 -0
  380. data/doc/Files/Transfer/UploadCoordinator/Region.html +295 -0
  381. data/doc/Files/Transfer/UploadCoordinator/Segments.html +299 -0
  382. data/doc/Files/Transfer/UploadCoordinator/Source.html +725 -0
  383. data/doc/Files/Transfer/UploadCoordinator/StreamSource.html +664 -0
  384. data/doc/Files/Transfer/UploadCoordinator.html +546 -0
  385. data/doc/Files/Transfer/UploadPlan.html +522 -0
  386. data/doc/Files/Transfer/VersionChangedError.html +235 -0
  387. data/doc/Files/Transfer.html +134 -0
  388. data/doc/Files/TrialExpiredError.html +1 -1
  389. data/doc/Files/TrialLockedError.html +1 -1
  390. data/doc/Files/TwoFactorAuthenticationCodeAlreadySentError.html +1 -1
  391. data/doc/Files/TwoFactorAuthenticationCountryBlacklistedError.html +1 -1
  392. data/doc/Files/TwoFactorAuthenticationErrorError.html +1 -1
  393. data/doc/Files/TwoFactorAuthenticationGeneralErrorError.html +1 -1
  394. data/doc/Files/TwoFactorAuthenticationMethodUnsupportedErrorError.html +1 -1
  395. data/doc/Files/TwoFactorAuthenticationRequiredError.html +1 -1
  396. data/doc/Files/TwoFactorAuthenticationSetupExpiredError.html +1 -1
  397. data/doc/Files/TwoFactorAuthenticationUnsubscribedRecipientError.html +1 -1
  398. data/doc/Files/URI.html +1 -1
  399. data/doc/Files/UnrecognizedSortIndexError.html +1 -1
  400. data/doc/Files/UnsupportedCurrencyError.html +1 -1
  401. data/doc/Files/UnsupportedHttpResponseFormatError.html +1 -1
  402. data/doc/Files/UnsupportedMediaTypeError.html +1 -1
  403. data/doc/Files/UpdatesNotAllowedForRemotesError.html +1 -1
  404. data/doc/Files/UploadsUnavailableError.html +1 -1
  405. data/doc/Files/UsageByTopLevelDir.html +1 -1
  406. data/doc/Files/UsageDailySnapshot.html +1 -1
  407. data/doc/Files/UsageSnapshot.html +1 -1
  408. data/doc/Files/User.html +1 -1
  409. data/doc/Files/UserAdditionalEmailRecipient.html +1 -1
  410. data/doc/Files/UserCipherUse.html +1 -1
  411. data/doc/Files/UserIdInvalidError.html +1 -1
  412. data/doc/Files/UserIdOnUserEndpointError.html +1 -1
  413. data/doc/Files/UserIdWithoutSiteAdminError.html +1 -1
  414. data/doc/Files/UserLifecycleRule.html +1 -1
  415. data/doc/Files/UserNotFoundError.html +1 -1
  416. data/doc/Files/UserRequest.html +1 -1
  417. data/doc/Files/UserRequestsEnabledRequiredError.html +1 -1
  418. data/doc/Files/UserRequiredError.html +1 -1
  419. data/doc/Files/UserSecurityEvent.html +1 -1
  420. data/doc/Files/UserSftpClientUse.html +1 -1
  421. data/doc/Files/Util/LogSettings.html +1 -1
  422. data/doc/Files/Util.html +1 -1
  423. data/doc/Files/WebDavActionLog.html +1 -1
  424. data/doc/Files/WebhookTest.html +1 -1
  425. data/doc/Files/Workspace.html +1 -1
  426. data/doc/Files/WriteAndBundlePermissionRequiredError.html +1 -1
  427. data/doc/Files/WritePermissionRequiredError.html +1 -1
  428. data/doc/Files/ZipListEntry.html +1 -1
  429. data/doc/Files.html +116 -116
  430. data/doc/_index.html +314 -4
  431. data/doc/class_list.html +1 -1
  432. data/doc/file.README.html +1 -1
  433. data/doc/index.html +1 -1
  434. data/doc/method_list.html +4703 -2415
  435. data/doc/top-level-namespace.html +1 -1
  436. data/docs/file.md +346 -0
  437. data/lib/files.com/api.rb +6 -1
  438. data/lib/files.com/api_client.rb +220 -32
  439. data/lib/files.com/models/file.rb +124 -17
  440. data/lib/files.com/transfer/budgets.rb +123 -0
  441. data/lib/files.com/transfer/checkpoint.rb +113 -0
  442. data/lib/files.com/transfer/clock.rb +14 -0
  443. data/lib/files.com/transfer/controller.rb +549 -0
  444. data/lib/files.com/transfer/download_coordinator.rb +906 -0
  445. data/lib/files.com/transfer/errors.rb +100 -0
  446. data/lib/files.com/transfer/http_transport.rb +337 -0
  447. data/lib/files.com/transfer/job.rb +778 -0
  448. data/lib/files.com/transfer/options.rb +126 -0
  449. data/lib/files.com/transfer/policy.rb +91 -0
  450. data/lib/files.com/transfer/storage_retry.rb +169 -0
  451. data/lib/files.com/transfer/upload_coordinator.rb +657 -0
  452. data/lib/files.com/transfer/upload_plan.rb +157 -0
  453. data/lib/files.com.rb +13 -0
  454. data/spec/lib/api_client_spec.rb +125 -3
  455. data/spec/models/file_transfer_spec.rb +53 -4
  456. data/spec/support/transfer_server.rb +113 -14
  457. data/spec/transfer/adaptive_download_spec.rb +1102 -0
  458. data/spec/transfer/adaptive_upload_spec.rb +1602 -0
  459. data/spec/transfer/concurrency_controller_spec.rb +382 -0
  460. data/spec/workspace_id_spec.rb +19 -14
  461. metadata +59 -1
@@ -0,0 +1,549 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Files
4
+ module Transfer
5
+ # Decides how many requests to one destination may be in flight at once, from those requests' own completions.
6
+ #
7
+ # It measures in windows. A window closes once it has accepted at least max(window attempts, limit) attempts and
8
+ # lasted at least their mean duration, and is judged only when its requests on the network averaged at least 75% of
9
+ # the limit, so a transfer short of work, or waiting on the API, its source, admission or memory, teaches it
10
+ # nothing. A window's throughput is its accepted bytes over its wall time; its latency L is its attempts' summed
11
+ # durations over those bytes; Lmin is the lowest L of any window; and with I requests on the network on average,
12
+ # its queue estimate is Q = I * (1 - Lmin / L). An accepted attempt counts in the window it finishes in, whichever
13
+ # epoch it was sent in, except during a throttle's cooldown.
14
+ #
15
+ # A policy with a warm start begins below its start. Its first judged window warms the connection and its next sets
16
+ # the baseline. From a baseline that is not already queued it probes upward, adding ceil(n / 2) to n, or 1 after a
17
+ # rest. The attempts in flight once the raised target first fills are the probe's cohort, known by their sequence
18
+ # numbers; the probe is judged by the first window opened after the last of them ends, however it ended, or by the
19
+ # window then closing once max(window attempts, target) answers have arrived since the raise, whichever comes
20
+ # first. It is kept when that window's throughput reaches the baseline's times 1 + 0.5 * d / n and its warm queue
21
+ # estimate, I * (1 - reference median / its median), stays under d / 2, and the next probe follows. Medians are of
22
+ # each accepted attempt's duration per byte, so windows whose attempts carry different sizes compare alike, and the
23
+ # reference median is the fastest judged at the target the probe came from (at a baseline's own target, for a
24
+ # baseline). A rejected probe returns to n and rests HOLD_WINDOWS + GROWTH_INTERVAL_WINDOWS judged windows of
25
+ # ordinary size, as a queued baseline does; the last of them is the next baseline. Warming goes on so below the
26
+ # start: it ends there, or at once on a judged window of 96 MiB/s or more. A failure or throttle in any epoch
27
+ # abandons the probe and starts the rest again, at whatever target its shrink left, down to one. The warming window
28
+ # and the windows before a cohort settles close after target answers; a baseline's or a probe's window after the
29
+ # answers the sampling rule below asks for, though one at 96 MiB/s closes after target answers.
30
+ #
31
+ # After that, each judged window grows the limit by the growth step (1 at or above soft) as a probe, keeps a probe
32
+ # whose window beats the one before by 1%, and gives it up after two windows that do not, holding a window. Q at
33
+ # queue_suppress stops growth, and at queue_backoff shrinks the limit by 8%, not below the latency floor.
34
+ #
35
+ # An attempt counts against the limit from when it is admitted (admit), but is on the network, and measured, only
36
+ # from when the transport starts sending it (sending) until it is answered (answered): an upload's attempt as its
37
+ # response headers arrive, a download's once its whole body has arrived and been checked. Waiting for a worker,
38
+ # reading a part's bytes before the send, writing to the destination and settling the outcome afterwards are not
39
+ # network time. Each attempt carries the epoch it was sent in, and every change of limit starts a new one. A failure
40
+ # shrinks the limit by 35% and a throttle by 10%, only for an attempt from the current epoch; every throttle also
41
+ # starts or extends a cooldown, during which completions are not measured, and restarts measurement, but only a
42
+ # change of limit starts a new epoch, so a throttle from an older one leaves the newer attempts' failures counting.
43
+ # A throttle's Retry-After holds its destination back through the job (Holds), whatever controller its requests
44
+ # use. A fixed policy never changes its limit. Its methods lock the controller, so any thread may call them.
45
+ class Controller
46
+ Attempt = Struct.new(:sequence, :epoch, :started_at, :ended_at)
47
+ MAX_DECISIONS = 256
48
+ MAX_SAMPLES = 1024
49
+
50
+ attr_reader :policy
51
+
52
+ def initialize(policy, clock: Clock)
53
+ @policy = policy
54
+ @clock = clock
55
+ @lock = Mutex.new
56
+ @limit = policy.initial
57
+ @phase = if policy.fixed?
58
+ :fixed
59
+ elsif policy.warm?
60
+ :warm
61
+ else
62
+ :ordinary
63
+ end
64
+ # While warming: :warming, :baseline, :settling (a probe's cohort is still running), :probing or :resting.
65
+ @stage = :warming
66
+ @epoch = 0
67
+ @sequence = 0
68
+ @admitted = 0
69
+ @inflight = 0
70
+ # Sequence numbers of the attempts admitted and not yet finished.
71
+ @outstanding = {}
72
+ @cohort = nil
73
+ @answers = 0
74
+ @warm_spread = 0.0
75
+ @warm_degrees = 0
76
+ @warm_medians = {}
77
+ @cooldown_until = nil
78
+ @latency_min = nil
79
+ @decisions = []
80
+ @warm_target = 2
81
+ @warm_probe = nil
82
+ @rest_windows = 0
83
+ @rested = false
84
+ @probe = nil
85
+ @hold_windows = 0
86
+ @judged_windows = 0
87
+ @ineligible_windows = 0
88
+ now = @clock.now
89
+ reset_window(now)
90
+ record(now, :start, @limit)
91
+ end
92
+
93
+ def limit
94
+ @lock.synchronize { @limit }
95
+ end
96
+
97
+ # Attempts on the network now: from the start of their send until they are answered (see answered).
98
+ def inflight
99
+ @lock.synchronize { @inflight }
100
+ end
101
+
102
+ # Attempts admitted and not yet finished, sent or not: what the limit bounds.
103
+ def admitted
104
+ @lock.synchronize { @admitted }
105
+ end
106
+
107
+ def phase
108
+ @lock.synchronize { @phase }
109
+ end
110
+
111
+ # Its decisions, oldest first and at most the last 256, each a frozen Hash of :at (its clock's seconds),
112
+ # :phase, :from, :to and :reason.
113
+ def decisions
114
+ @lock.synchronize { @decisions.dup }
115
+ end
116
+
117
+ # Its policy, phase, limit, attempts admitted, attempts on the network (inflight) and epoch, with how many
118
+ # measurement windows it judged and how many it closed without judging because too few requests were on the
119
+ # network (judged_windows and ineligible_windows).
120
+ def snapshot
121
+ @lock.synchronize do
122
+ { policy: @policy.name, phase: @phase, limit: @limit, admitted: @admitted, inflight: @inflight, epoch: @epoch,
123
+ judged_windows: @judged_windows, ineligible_windows: @ineligible_windows }
124
+ end
125
+ end
126
+
127
+ # Counts an attempt against the limit from now, before it is sent, with the next sequence number; sending,
128
+ # answered and finish_attempt take what it returns.
129
+ def admit
130
+ @lock.synchronize do
131
+ @admitted += 1
132
+ attempt = Attempt.new(@sequence += 1)
133
+ @outstanding[attempt.sequence] = true
134
+ note_cohort
135
+ attempt
136
+ end
137
+ end
138
+
139
+ # As the transport starts sending attempt, connection setup included: it is in flight, in the current epoch, from
140
+ # now.
141
+ def sending(attempt)
142
+ @lock.synchronize do
143
+ return if attempt.started_at
144
+
145
+ now = @clock.now
146
+ advance(now)
147
+ @inflight += 1
148
+ attempt.epoch = @epoch
149
+ attempt.started_at = now
150
+ end
151
+ end
152
+
153
+ # As attempt is answered, an upload's as its response headers arrive and a download's once its whole body has
154
+ # arrived and been checked: it is no longer on the network from now. Only the first call counts.
155
+ def answered(attempt)
156
+ @lock.synchronize { answer(attempt, @clock.now) }
157
+ end
158
+
159
+ # Admits an attempt and starts sending it at once.
160
+ def start_attempt
161
+ attempt = admit
162
+ sending(attempt)
163
+ attempt
164
+ end
165
+
166
+ # outcome is :success, with the bytes the attempt carried; :failure, for an error worth retrying; :throttle, for
167
+ # a 429 or 503, with its Retry-After seconds; or :ignored, for what says nothing about capacity, such as a
168
+ # cancellation, a refused or expired request or the caller's source or callback failing. An attempt never sent
169
+ # teaches nothing, but every attempt ending counts toward settling a probe's cohort. Returns the limit.
170
+ def finish_attempt(attempt, outcome, bytes: 0, retry_after: nil)
171
+ @lock.synchronize do
172
+ now = @clock.now
173
+ answer(attempt, now)
174
+ advance(now)
175
+ @admitted -= 1
176
+ @outstanding.delete(attempt.sequence)
177
+ if attempt.started_at
178
+ case outcome
179
+ when :success then measure(attempt, now, bytes)
180
+ when :failure then failed(attempt, now)
181
+ when :throttle then throttled(attempt, now, retry_after)
182
+ end
183
+ end
184
+ settle_cohort(attempt, now)
185
+ @limit
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ def answer(attempt, now)
192
+ return unless attempt.started_at && attempt.ended_at.nil?
193
+
194
+ advance(now)
195
+ @inflight -= 1
196
+ attempt.ended_at = now
197
+ end
198
+
199
+ # Adds the time since the last event to the window's requests-in-flight integral. A window restarts when a
200
+ # cooldown ends, and nothing accumulates during one.
201
+ def advance(now)
202
+ if @cooldown_until
203
+ return if now < @cooldown_until
204
+
205
+ reset_window(@cooldown_until)
206
+ @cooldown_until = nil
207
+ end
208
+ @window[:busy] += @inflight * (now - @window[:last_change])
209
+ @window[:last_change] = now
210
+ end
211
+
212
+ def reset_window(now)
213
+ @window = { started_at: now, last_change: now, busy: 0.0, attempts: 0, bytes: 0, durations: 0.0, samples: [] }
214
+ end
215
+
216
+ # An accepted attempt, in the current window whichever epoch it was sent in, unless a cooldown is running.
217
+ def measure(attempt, now, bytes)
218
+ return if @phase == :fixed || @cooldown_until
219
+
220
+ window = @window
221
+ duration = attempt.ended_at - attempt.started_at
222
+ window[:attempts] += 1
223
+ window[:bytes] += bytes
224
+ window[:durations] += duration
225
+ window[:samples] << (duration / bytes) if bytes.positive? && window[:samples].size < MAX_SAMPLES
226
+ answered_since_raise
227
+ elapsed = now - window[:started_at]
228
+ return unless elapsed >= window[:durations] / window[:attempts]
229
+ return unless window[:attempts] >= window_target || fast_window?(window, elapsed)
230
+
231
+ close_window(now)
232
+ end
233
+
234
+ # A warm window already at 96 MiB/s or more closes once target answers have arrived, without more samples.
235
+ def fast_window?(window, elapsed)
236
+ @phase == :warm && window[:attempts] >= @limit && elapsed.positive? && window[:bytes] / elapsed >= Policy::FAST_LINK_BYTES_PER_SECOND
237
+ end
238
+
239
+ # The answers a window needs: max(window attempts, limit), except while warming: the limit for the warming window
240
+ # and the windows before a probe's cohort settles, and what the sampling rule asks for, at least the limit, for a
241
+ # baseline's or a probe's window. A rest's windows are of the ordinary size.
242
+ def window_target
243
+ ordinary = [ @policy.window_attempts, @limit ].max
244
+ return ordinary unless @phase == :warm
245
+
246
+ case @stage
247
+ when :baseline, :probing then [ @limit, @warm_target ].max
248
+ when :resting then ordinary
249
+ else @limit
250
+ end
251
+ end
252
+
253
+ def close_window(now)
254
+ window = @window
255
+ elapsed = now - window[:started_at]
256
+ reset_window(now)
257
+ return unless elapsed.positive?
258
+
259
+ stats = {
260
+ inflight: window[:busy] / elapsed,
261
+ throughput: window[:bytes] / elapsed,
262
+ latency: window[:bytes].positive? ? window[:durations] / window[:bytes] : nil,
263
+ samples: window[:samples]
264
+ }
265
+ latency = stats[:latency]
266
+ @latency_min = latency if !latency.nil? && latency.positive? && (@latency_min.nil? || latency < @latency_min)
267
+ if stats[:inflight] < Policy::ELIGIBLE_OCCUPANCY * @limit
268
+ @ineligible_windows += 1
269
+ record(now, :ineligible, @limit)
270
+ elsif @phase == :warm
271
+ @judged_windows += 1
272
+ judge_warm(now, stats)
273
+ else
274
+ @judged_windows += 1
275
+ judge(now, stats)
276
+ end
277
+ end
278
+
279
+ # A judged warm window. The fast-link check comes first, whatever the window was for.
280
+ def judge_warm(now, stats)
281
+ note_warm(stats[:samples]) unless @stage == :settling
282
+ return promote(now) if stats[:throughput] >= Policy::FAST_LINK_BYTES_PER_SECOND
283
+
284
+ case @stage
285
+ when :warming
286
+ @stage = :baseline
287
+ @warm_target = required_samples(@limit, [ next_added, 1 ].max)
288
+ record(now, :warm_connection, @limit)
289
+ when :settling then record(now, :warm_settling, @limit)
290
+ when :baseline then rest_or_probe(now, stats)
291
+ when :probing then judge_probe(now, stats)
292
+ else
293
+ @rest_windows -= 1
294
+ @rest_windows.positive? ? record(now, :rest, @limit) : rest_or_probe(now, stats)
295
+ end
296
+ end
297
+
298
+ def promote(now)
299
+ @warm_probe = nil
300
+ @cohort = nil
301
+ @phase = :ordinary
302
+ change(now, [ @policy.start, @limit ].max, :fast_link)
303
+ end
304
+
305
+ # From a judged baseline, or the last window of a rest: rests again when that window's own warm queue estimate is
306
+ # already at least half what the next probe would add, and otherwise raises the target by that much.
307
+ def rest_or_probe(now, stats)
308
+ added = next_added
309
+ return finish_warm(now, :warm_complete) unless added.positive?
310
+ return rest(now, :warm_queued) if warm_queue(@limit, stats) >= 0.5 * added
311
+
312
+ @warm_probe = { from: @limit, added: added, reference: stats }
313
+ @warm_target = required_samples(@limit, added)
314
+ @stage = :settling
315
+ @cohort = nil
316
+ @answers = 0
317
+ change(now, @limit + added, :warm_probe)
318
+ note_cohort
319
+ end
320
+
321
+ # The window judging a probe: the raise is kept, and the next probe follows, when its throughput beats the
322
+ # baseline's by half the share of attempts added and its warm queue estimate stays under d / 2; otherwise the
323
+ # target goes back to where the probe came from, to rest.
324
+ def judge_probe(now, stats)
325
+ probe = @warm_probe
326
+ return rest_or_probe(now, stats) unless probe
327
+
328
+ @warm_probe = nil
329
+ required = probe[:reference][:throughput] * (1 + (0.5 * probe[:added] / probe[:from]))
330
+ if stats[:throughput] >= required && warm_queue(probe[:from], stats, probe[:reference]) < 0.5 * probe[:added]
331
+ @rested = false
332
+ return finish_warm(now, :warm_complete) if @limit >= @policy.start
333
+
334
+ rest_or_probe(now, stats)
335
+ else
336
+ change(now, probe[:from], :warm_reject)
337
+ rest(now)
338
+ end
339
+ end
340
+
341
+ # Rests HOLD_WINDOWS + GROWTH_INTERVAL_WINDOWS judged windows of ordinary size; the probe after it adds one.
342
+ def rest(now, reason = nil)
343
+ @stage = :resting
344
+ @rest_windows = Policy::HOLD_WINDOWS + Policy::GROWTH_INTERVAL_WINDOWS
345
+ @rested = true
346
+ @warm_probe = nil
347
+ @cohort = nil
348
+ record(now, reason, @limit) if reason
349
+ end
350
+
351
+ # What the next warm probe adds: half the target, rounded up, or one after a rest, never past the start.
352
+ def next_added
353
+ [ @rested ? 1 : (@limit / 2.0).ceil, @policy.start - @limit ].min
354
+ end
355
+
356
+ def finish_warm(now, reason)
357
+ @phase = :ordinary
358
+ record(now, reason, @limit)
359
+ end
360
+
361
+ # The attempts in flight when a probe's raised target first fills are its cohort, by their sequence numbers.
362
+ def note_cohort
363
+ return unless @phase == :warm && @stage == :settling && @cohort.nil? && @admitted >= @limit
364
+
365
+ @cohort = @outstanding.keys.to_set
366
+ end
367
+
368
+ # Once every attempt of a probe's cohort has ended, however it ended, the window opened then judges the probe.
369
+ def settle_cohort(attempt, now)
370
+ return unless @cohort&.delete?(attempt.sequence) && @cohort&.empty?
371
+
372
+ @cohort = nil
373
+ return unless @phase == :warm && @stage == :settling
374
+
375
+ @stage = :probing
376
+ reset_window(now)
377
+ record(now, :warm_settled, @limit)
378
+ end
379
+
380
+ # An accepted answer since a probe's raise: once max(window attempts, target) of them have arrived, the window
381
+ # then closing judges the probe, whether its cohort has settled or not.
382
+ def answered_since_raise
383
+ return unless @phase == :warm && @stage == :settling
384
+
385
+ @answers += 1
386
+ return if @answers < [ @policy.window_attempts, @limit ].max
387
+
388
+ @stage = :probing
389
+ @cohort = nil
390
+ end
391
+
392
+ # A judged warm window's samples, each an accepted attempt's duration over its bytes: the fastest median at its
393
+ # target, and their squared deviations from their mean, relative to its square, with their degrees of freedom.
394
+ def note_warm(samples)
395
+ median = median(samples)
396
+ best = @warm_medians[@limit]
397
+ @warm_medians[@limit] = median if !median.nil? && median.positive? && (best.nil? || median < best)
398
+ return if samples.size < 2
399
+
400
+ mean = samples.sum / samples.size
401
+ return unless mean.positive?
402
+
403
+ @warm_spread += samples.sum { |sample| ((sample / mean) - 1)**2 }
404
+ @warm_degrees += samples.size - 1
405
+ end
406
+
407
+ # I * (1 - reference median / current median duration per byte): how much of a window's concurrency queued,
408
+ # against the fastest median judged at target, or reference's when none was.
409
+ def warm_queue(target, stats, reference = nil)
410
+ before = @warm_medians[target] || (reference && median(reference[:samples]))
411
+ after = median(stats[:samples])
412
+ return 0.0 unless before&.positive? && after&.positive?
413
+
414
+ stats[:inflight] * (1 - (before / after))
415
+ end
416
+
417
+ def median(samples)
418
+ sorted = samples.sort
419
+ return if sorted.empty?
420
+
421
+ middle = sorted.size / 2
422
+ sorted.size.odd? ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2.0
423
+ end
424
+
425
+ # How many attempts the probe window needs to tell a gain of 0.5 * d / n from noise. With S the relative squared
426
+ # deviations note_warm summed over every judged warm window so far, this one included, and f their degrees of
427
+ # freedom, a = 2 / (9f) and h = 1 - a - 2 * sqrt(a), v = S / (f * h^3) bounds their variance from above (no
428
+ # bound when h is not positive), and the probe at its raised target r = n + d needs
429
+ # max(2, ceil(2 * v * (r * 2 / (0.5d))^2)), at most the window attempts; two without any spread or freedom.
430
+ def required_samples(from, added)
431
+ return 2 if @warm_degrees < 1 || !@warm_spread.positive? || !added.positive?
432
+
433
+ a = 2.0 / (9 * @warm_degrees)
434
+ h = 1 - a - (2 * Math.sqrt(a))
435
+ return @policy.window_attempts unless h.positive?
436
+
437
+ variance = @warm_spread / (@warm_degrees * (h**3))
438
+ raised = from + added
439
+ (2 * variance * (((raised * 2.0) / (0.5 * added))**2)).ceil.clamp(2, @policy.window_attempts)
440
+ end
441
+
442
+ def judge(now, stats)
443
+ queue = queue_estimate(stats)
444
+ probe = @probe
445
+ if queue >= @policy.queue_backoff
446
+ @probe = nil
447
+ shrink(now, Policy::LATENCY_SHRINK, @policy.latency_floor, :queue_backoff)
448
+ elsif probe
449
+ if queue < @policy.queue_suppress && stats[:throughput] >= probe[:baseline] * (1 + Policy::PROBE_GAIN)
450
+ @probe = nil
451
+ grow(now, stats, :probe_earned)
452
+ elsif queue < @policy.queue_suppress && (probe[:misses] += 1) < Policy::PROBE_WINDOWS
453
+ record(now, :probe_miss, @limit)
454
+ else
455
+ @probe = nil
456
+ @hold_windows = Policy::HOLD_WINDOWS
457
+ change(now, [ probe[:from], [ @policy.rejected_floor, @limit ].min ].max, :probe_rejected)
458
+ end
459
+ elsif queue >= @policy.queue_suppress
460
+ record(now, :queue_suppress, @limit)
461
+ elsif @hold_windows.positive?
462
+ @hold_windows -= 1
463
+ record(now, :hold, @limit)
464
+ else
465
+ grow(now, stats, :probe)
466
+ end
467
+ end
468
+
469
+ def queue_estimate(stats)
470
+ latency = stats[:latency]
471
+ return 0.0 unless latency&.positive? && @latency_min
472
+
473
+ stats[:inflight] * (1 - (@latency_min / latency))
474
+ end
475
+
476
+ def grow(now, stats, reason)
477
+ return record(now, reason == :probe ? :at_max : reason, @limit) if @limit >= @policy.max
478
+
479
+ step = @limit < @policy.soft ? @policy.growth_step : 1
480
+ @probe = { from: @limit, baseline: stats[:throughput], misses: 0 }
481
+ change(now, [ @limit + step, @policy.max ].min, reason)
482
+ end
483
+
484
+ # Shrinks the limit by share, to no less than floor (or the limit, when that is lower) and by at least one.
485
+ # Returns whether it changed.
486
+ def shrink(now, share, floor, reason)
487
+ target = [ (@limit * (1 - share)).floor, @limit - 1 ].min
488
+ target = [ target, [ floor, @limit ].min, 1 ].max
489
+ return false if target >= @limit
490
+
491
+ change(now, target, reason)
492
+ true
493
+ end
494
+
495
+ # A failure shrinks the limit only for an attempt from the current epoch; while warming, down to one, and in any
496
+ # epoch it abandons the probe and starts the rest again.
497
+ def failed(attempt, now)
498
+ return if @phase == :fixed
499
+ return warm_interrupted(now, attempt.epoch == @epoch && shrink(now, Policy::FAILURE_SHRINK, 1, :failure)) if @phase == :warm
500
+ return unless attempt.epoch == @epoch
501
+
502
+ @probe = nil
503
+ shrink(now, Policy::FAILURE_SHRINK, @policy.min, :failure)
504
+ end
505
+
506
+ # A throttle shrinks the limit only for an attempt from the current epoch (while warming, down to one), which starts
507
+ # a new epoch. Every throttle, in any epoch, starts or extends a cooldown and restarts measurement; one that changed
508
+ # no limit keeps the epoch, so attempts sent since the last change still count their failures. While warming, in
509
+ # any epoch, it abandons the probe and rests again. A fixed policy learns nothing from it.
510
+ def throttled(attempt, now, retry_after)
511
+ return if @phase == :fixed
512
+
513
+ cooldown = now + [ Policy::THROTTLE_COOLDOWN, retry_after.to_f ].max
514
+ @cooldown_until = [ @cooldown_until || cooldown, cooldown ].max
515
+ current = attempt.epoch == @epoch
516
+ @probe = nil if current
517
+ shrunk = current && shrink(now, Policy::THROTTLE_SHRINK, @phase == :warm ? 1 : @policy.min, :throttle)
518
+ unless shrunk
519
+ reset_window(now)
520
+ record(now, :throttle_cooldown, @limit)
521
+ end
522
+ rest(now) if @phase == :warm
523
+ end
524
+
525
+ # A failure while warming, in any epoch: the probe is abandoned without being judged, and the rest starts again,
526
+ # at whatever target the failure left.
527
+ def warm_interrupted(now, shrunk)
528
+ reset_window(now) unless shrunk
529
+ rest(now, shrunk ? nil : :warm_interrupted)
530
+ end
531
+
532
+ def change(now, to, reason)
533
+ from = @limit
534
+ if to != from
535
+ @limit = to
536
+ @epoch += 1
537
+ reset_window(now)
538
+ end
539
+ record(now, reason, from)
540
+ end
541
+
542
+ def record(now, reason, from)
543
+ @decisions.shift if @decisions.size >= MAX_DECISIONS
544
+ @decisions << { at: now, phase: @phase, from: from, to: @limit, reason: reason }.freeze
545
+ nil
546
+ end
547
+ end
548
+ end
549
+ end