5
5
6
6
import { Octokit } from '@octokit/rest' ;
7
7
import { ApolloClient , ApolloQueryResult , FetchResult , MutationOptions , NormalizedCacheObject , OperationVariables , QueryOptions } from 'apollo-boost' ;
8
+ import { bulkhead , BulkheadPolicy } from 'cockatiel' ;
9
+ import * as vscode from 'vscode' ;
8
10
import Logger from '../common/logger' ;
9
11
import { ITelemetry } from '../common/telemetry' ;
10
12
import { RateLimit } from './graphql' ;
@@ -17,11 +19,27 @@ interface RestResponse {
17
19
}
18
20
19
21
export class RateLogger {
22
+ private bulkhead : BulkheadPolicy = bulkhead ( 140 ) ;
20
23
private static ID = 'RateLimit' ;
21
24
private hasLoggedLowRateLimit : boolean = false ;
22
25
23
26
constructor ( private readonly telemetry : ITelemetry ) { }
24
27
28
+ public logAndLimit ( apiRequest : ( ) => Promise < any > ) : Promise < any > | undefined {
29
+ if ( this . bulkhead . executionSlots === 0 ) {
30
+ Logger . error ( 'API call count has exceeded 140 concurrent calls.' , RateLogger . ID ) ;
31
+ // We have hit more than 140 concurrent API requests.
32
+ /* __GDPR__
33
+ "pr.highApiCallRate" : {}
34
+ */
35
+ this . telemetry . sendTelemetryErrorEvent ( 'pr.highApiCallRate' ) ;
36
+ vscode . window . showErrorMessage ( vscode . l10n . t ( 'The GitHub Pull Requests extension is making too many requests to GitHub. This indicates a bug in the extension. Please file an issue on GitHub and include the output from "GitHub Pull Request".' ) ) ;
37
+ return undefined ;
38
+ }
39
+ Logger . debug ( `Extension rate limit remaining: ${ this . bulkhead . executionSlots } ` , RateLogger . ID ) ;
40
+ return this . bulkhead . execute ( ( ) => apiRequest ( ) ) ;
41
+ }
42
+
25
43
public async logRateLimit ( info : string | undefined , result : Promise < { data : { rateLimit : RateLimit | undefined } | undefined } | undefined > , isRest : boolean = false ) {
26
44
let rateLimitInfo ;
27
45
try {
@@ -70,13 +88,19 @@ export class LoggingApolloClient {
70
88
constructor ( private readonly _graphql : ApolloClient < NormalizedCacheObject > , private _rateLogger : RateLogger ) { } ;
71
89
72
90
query < T = any , TVariables = OperationVariables > ( options : QueryOptions < TVariables > ) : Promise < ApolloQueryResult < T > > {
73
- const result = this . _graphql . query ( options ) ;
91
+ const result = this . _rateLogger . logAndLimit ( ( ) => this . _graphql . query ( options ) ) ;
92
+ if ( result === undefined ) {
93
+ throw new Error ( 'API call count has exceeded a rate limit.' ) ;
94
+ }
74
95
this . _rateLogger . logRateLimit ( ( options . query . definitions [ 0 ] as { name : { value : string } | undefined } ) . name ?. value , result as any ) ;
75
96
return result ;
76
97
}
77
98
78
99
mutate < T = any , TVariables = OperationVariables > ( options : MutationOptions < T , TVariables > ) : Promise < FetchResult < T > > {
79
- const result = this . _graphql . mutate ( options ) ;
100
+ const result = this . _rateLogger . logAndLimit ( ( ) => this . _graphql . mutate ( options ) ) ;
101
+ if ( result === undefined ) {
102
+ throw new Error ( 'API call count has exceeded a rate limit.' ) ;
103
+ }
80
104
this . _rateLogger . logRateLimit ( options . context , result as any ) ;
81
105
return result ;
82
106
}
@@ -86,7 +110,10 @@ export class LoggingOctokit {
86
110
constructor ( public readonly api : Octokit , private _rateLogger : RateLogger ) { } ;
87
111
88
112
async call < T , U > ( api : ( T ) => Promise < U > , args : T ) : Promise < U > {
89
- const result = api ( args ) ;
113
+ const result = this . _rateLogger . logAndLimit ( ( ) => api ( args ) ) ;
114
+ if ( result === undefined ) {
115
+ throw new Error ( 'API call count has exceeded a rate limit.' ) ;
116
+ }
90
117
this . _rateLogger . logRestRateLimit ( ( api as unknown as { endpoint : { DEFAULTS : { url : string } | undefined } | undefined } ) . endpoint ?. DEFAULTS ?. url , result as Promise < unknown > as Promise < RestResponse > ) ;
91
118
return result ;
92
119
}
0 commit comments