@@ -12,26 +12,27 @@ import { IChatMLFetcher, IntentParams, Source } from '../../src/platform/chat/co
12
12
import { ChatFetchResponseType , ChatLocation , ChatResponses } from '../../src/platform/chat/common/commonTypes' ;
13
13
import { IConversationOptions } from '../../src/platform/chat/common/conversationOptions' ;
14
14
import { getTextPart } from '../../src/platform/chat/common/globalStringUtils' ;
15
+ import { LogLevel } from '../../src/platform/log/common/logService' ;
15
16
import { FinishedCallback , ICopilotToolCall , IResponseDelta , OptionalChatRequestParams } from '../../src/platform/networking/common/fetch' ;
16
17
import { IChatEndpoint } from '../../src/platform/networking/common/networking' ;
17
18
import { ChoiceLogProbs , rawMessageToCAPI } from '../../src/platform/networking/common/openai' ;
18
19
import { TelemetryProperties } from '../../src/platform/telemetry/common/telemetry' ;
19
20
import { LcsDiff , LineSequence } from '../../src/util/common/diff' ;
21
+ import { LockMap } from '../../src/util/common/lock' ;
20
22
import { BugIndicatingError } from '../../src/util/vs/base/common/errors' ;
21
23
import { IDisposable } from '../../src/util/vs/base/common/lifecycle' ;
22
24
import { SyncDescriptor } from '../../src/util/vs/platform/instantiation/common/descriptors' ;
23
25
import { IInstantiationService } from '../../src/util/vs/platform/instantiation/common/instantiation' ;
24
26
import { CHAT_ML_CACHE_SALT } from '../cacheSalt' ;
25
27
import { IJSONOutputPrinter } from '../jsonOutputPrinter' ;
26
28
import { OutputType } from '../simulation/shared/sharedTypes' ;
29
+ import { logger } from '../simulationLogger' ;
27
30
import { computeSHA256 } from './hash' ;
28
31
import { CacheMode , NoFetchChatMLFetcher } from './simulationContext' ;
29
32
import { ISimulationEndpointHealth } from './simulationEndpointHealth' ;
30
33
import { SimulationOutcomeImpl } from './simulationOutcome' ;
31
34
import { drainStdoutAndExit } from './stdout' ;
32
35
import { REPO_ROOT , SimulationTest } from './stest' ;
33
- import { logger } from '../simulationLogger' ;
34
- import { LogLevel } from '../../src/platform/log/common/logService' ;
35
36
36
37
export class CacheableChatRequest {
37
38
public readonly hash : string ;
@@ -106,6 +107,9 @@ export type ResponseWithMeta = ChatResponses & {
106
107
107
108
108
109
export class CachingChatMLFetcher extends AbstractChatMLFetcher implements IDisposable {
110
+
111
+ private static readonly Locks = new LockMap ( ) ;
112
+
109
113
private readonly fetcher : IChatMLFetcher ;
110
114
private isDisposed = false ;
111
115
@@ -167,113 +171,115 @@ export class CachingChatMLFetcher extends AbstractChatMLFetcher implements IDisp
167
171
const req = new CacheableChatRequest ( messages , endpoint . model , finalReqOptions , intentParams , this . extraCacheProperties ) ;
168
172
// console.log(`request with hash: ${req.hash}`);
169
173
170
- let isCacheHit : boolean | undefined = undefined ;
171
- if ( this . cacheMode !== CacheMode . Disable ) {
172
- const cacheValue = await this . cache . get ( req , this . testInfo . cacheSlot ) ;
173
- if ( cacheValue ) {
174
- if ( cacheValue . type === ChatFetchResponseType . Success ) {
175
- await finishedCb ?.( cacheValue . value [ 0 ] , 0 , { text : cacheValue . value [ 0 ] , copilotToolCalls : cacheValue . copilotFunctionCalls , logprobs : cacheValue . logprobs } ) ;
176
- } else if ( cacheValue . type === ChatFetchResponseType . Length ) {
177
- await finishedCb ?.( cacheValue . truncatedValue , 0 , { text : cacheValue . truncatedValue , copilotToolCalls : cacheValue . copilotFunctionCalls , logprobs : cacheValue . logprobs } ) ;
174
+ return CachingChatMLFetcher . Locks . withLock ( req . hash , async ( ) => {
175
+ let isCacheHit : boolean | undefined = undefined ;
176
+ if ( this . cacheMode !== CacheMode . Disable ) {
177
+ const cacheValue = await this . cache . get ( req , this . testInfo . cacheSlot ) ;
178
+ if ( cacheValue ) {
179
+ if ( cacheValue . type === ChatFetchResponseType . Success ) {
180
+ await finishedCb ?.( cacheValue . value [ 0 ] , 0 , { text : cacheValue . value [ 0 ] , copilotToolCalls : cacheValue . copilotFunctionCalls , logprobs : cacheValue . logprobs } ) ;
181
+ } else if ( cacheValue . type === ChatFetchResponseType . Length ) {
182
+ await finishedCb ?.( cacheValue . truncatedValue , 0 , { text : cacheValue . truncatedValue , copilotToolCalls : cacheValue . copilotFunctionCalls , logprobs : cacheValue . logprobs } ) ;
183
+ }
184
+ return { ...cacheValue , isCacheHit : true , cacheKey : req . hash } ;
178
185
}
179
- return { ...cacheValue , isCacheHit : true , cacheKey : req . hash } ;
180
- }
181
- isCacheHit = false ;
182
- }
183
-
184
- if ( this . cacheMode === CacheMode . Require ) {
185
- let diff : { newRequest : string ; oldRequest : string } | undefined ;
186
- try {
187
- diff = await this . suggestDiffCommandForCacheMiss ( req ) ;
188
- } catch ( err ) {
189
- console . log ( err ) ;
186
+ isCacheHit = false ;
190
187
}
191
188
192
- console . log ( JSON . stringify ( messages , ( key , value ) => {
193
- if ( typeof value === 'string' ) {
194
- const split = value . split ( / \n / g) ;
195
- return split . length > 1 ? split : value ;
189
+ if ( this . cacheMode === CacheMode . Require ) {
190
+ let diff : { newRequest : string ; oldRequest : string } | undefined ;
191
+ try {
192
+ diff = await this . suggestDiffCommandForCacheMiss ( req ) ;
193
+ } catch ( err ) {
194
+ console . log ( err ) ;
196
195
}
197
- return value ;
198
- } , 4 ) ) ;
199
196
200
- let message = `\n✗ Cache entry not found for a request generated by test "${ this . testInfo . testName } "!
197
+ console . log ( JSON . stringify ( messages , ( key , value ) => {
198
+ if ( typeof value === 'string' ) {
199
+ const split = value . split ( / \n / g) ;
200
+ return split . length > 1 ? split : value ;
201
+ }
202
+ return value ;
203
+ } , 4 ) ) ;
204
+
205
+ let message = `\n✗ Cache entry not found for a request generated by test "${ this . testInfo . testName } "!
201
206
- Valid cache entries are currently required for all requests!
202
207
- The missing request has the hash: ${ req . hash } (cache slot ${ this . testInfo . cacheSlot } , make sure to call simulate -- -n=10).
203
208
` ;
204
- if ( diff ) {
205
- message += `- Compare with the closest cache entry using \`code-insiders --diff "${ diff . oldRequest } " "${ diff . newRequest } "\`\n` ;
206
- }
209
+ if ( diff ) {
210
+ message += `- Compare with the closest cache entry using \`code-insiders --diff "${ diff . oldRequest } " "${ diff . newRequest } "\`\n` ;
211
+ }
207
212
208
- console . log ( message ) ;
209
- this . printTerminatedWithRequireCache ( message ) ;
210
- await drainStdoutAndExit ( 1 ) ;
211
- throw new Error ( message ) ;
212
- }
213
+ console . log ( message ) ;
214
+ this . printTerminatedWithRequireCache ( message ) ;
215
+ await drainStdoutAndExit ( 1 ) ;
216
+ throw new Error ( message ) ;
217
+ }
213
218
214
- const callbackWrapper = new FinishedCallbackWrapper ( finishedCb ) ;
215
- const start = Date . now ( ) ;
216
- if ( logger . shouldLog ( LogLevel . Trace ) ) {
217
- logger . trace ( `Making request:\n` + messages . map ( m => ` ${ m . role } : ${ getTextPart ( m . content ) } ` ) . join ( '\n' ) ) ;
218
- }
219
- const result = await this . fetcher . fetchMany (
220
- debugName ,
221
- messages ,
222
- callbackWrapper . getCb ( ) ,
223
- token ,
224
- location ,
225
- endpoint ,
226
- source ,
227
- requestOptions ,
228
- userInitiatedRequest ,
229
- telemetryProperties ,
230
- intentParams
231
- ) ;
232
- const fetchingResponseTimeInMs = Date . now ( ) - start ;
233
- // Don't cache failed results
234
- if (
235
- result . type === ChatFetchResponseType . OffTopic
236
- || result . type === ChatFetchResponseType . Filtered
237
- || result . type === ChatFetchResponseType . Length
238
- || result . type === ChatFetchResponseType . Success
239
- ) {
240
- const cacheMetadata : CachedResponseMetadata = {
241
- testName : this . testInfo . testName ,
242
- requestDuration : fetchingResponseTimeInMs ,
243
- requestTime : new Date ( ) . toISOString ( )
244
- } ;
245
- const cachedResponse : CachedResponse = {
246
- ...result ,
247
- cacheMetadata,
248
- copilotFunctionCalls : callbackWrapper . copilotFunctionCalls ,
249
- logprobs : callbackWrapper . logprobs ,
250
- } ;
251
- if ( ! ( this . fetcher instanceof NoFetchChatMLFetcher ) ) {
252
- try {
253
- await this . cache . set ( req , this . testInfo . cacheSlot , cachedResponse ) ;
254
- } catch ( err ) {
255
- if ( / K e y a l r e a d y e x i s t s / . test ( err . message ) ) {
256
- console . log ( JSON . stringify ( messages , ( key , value ) => {
257
- if ( typeof value === 'string' ) {
258
- const split = value . split ( / \n / g) ;
259
- return split . length > 1 ? split : value ;
260
- }
261
- return value ;
262
- } , 4 ) ) ;
263
- console . log ( `\n✗ ${ err . message } ` ) ;
264
- await drainStdoutAndExit ( 1 ) ;
219
+ const callbackWrapper = new FinishedCallbackWrapper ( finishedCb ) ;
220
+ const start = Date . now ( ) ;
221
+ if ( logger . shouldLog ( LogLevel . Trace ) ) {
222
+ logger . trace ( `Making request:\n` + messages . map ( m => ` ${ m . role } : ${ getTextPart ( m . content ) } ` ) . join ( '\n' ) ) ;
223
+ }
224
+ const result = await this . fetcher . fetchMany (
225
+ debugName ,
226
+ messages ,
227
+ callbackWrapper . getCb ( ) ,
228
+ token ,
229
+ location ,
230
+ endpoint ,
231
+ source ,
232
+ requestOptions ,
233
+ userInitiatedRequest ,
234
+ telemetryProperties ,
235
+ intentParams
236
+ ) ;
237
+ const fetchingResponseTimeInMs = Date . now ( ) - start ;
238
+ // Don't cache failed results
239
+ if (
240
+ result . type === ChatFetchResponseType . OffTopic
241
+ || result . type === ChatFetchResponseType . Filtered
242
+ || result . type === ChatFetchResponseType . Length
243
+ || result . type === ChatFetchResponseType . Success
244
+ ) {
245
+ const cacheMetadata : CachedResponseMetadata = {
246
+ testName : this . testInfo . testName ,
247
+ requestDuration : fetchingResponseTimeInMs ,
248
+ requestTime : new Date ( ) . toISOString ( )
249
+ } ;
250
+ const cachedResponse : CachedResponse = {
251
+ ...result ,
252
+ cacheMetadata,
253
+ copilotFunctionCalls : callbackWrapper . copilotFunctionCalls ,
254
+ logprobs : callbackWrapper . logprobs ,
255
+ } ;
256
+ if ( ! ( this . fetcher instanceof NoFetchChatMLFetcher ) ) {
257
+ try {
258
+ await this . cache . set ( req , this . testInfo . cacheSlot , cachedResponse ) ;
259
+ } catch ( err ) {
260
+ if ( / K e y a l r e a d y e x i s t s / . test ( err . message ) ) {
261
+ console . log ( JSON . stringify ( messages , ( key , value ) => {
262
+ if ( typeof value === 'string' ) {
263
+ const split = value . split ( / \n / g) ;
264
+ return split . length > 1 ? split : value ;
265
+ }
266
+ return value ;
267
+ } , 4 ) ) ;
268
+ console . log ( `\n✗ ${ err . message } ` ) ;
269
+ await drainStdoutAndExit ( 1 ) ;
270
+ }
271
+
272
+ throw err ;
265
273
}
266
-
267
- throw err ;
274
+ return { ...result , cacheMetadata, isCacheHit, cacheKey : req . hash } ;
268
275
}
269
- return { ...result , cacheMetadata, isCacheHit, cacheKey : req . hash } ;
276
+ } else {
277
+ // A request failed, so we don't want to cache it.
278
+ // But we should warn the developer that they need to rerun
279
+ this . simulationEndpointHealth . markFailure ( this . testInfo , result ) ;
270
280
}
271
- } else {
272
- // A request failed, so we don't want to cache it.
273
- // But we should warn the developer that they need to rerun
274
- this . simulationEndpointHealth . markFailure ( this . testInfo , result ) ;
275
- }
276
- return { ...result , isCacheHit } ;
281
+ return { ...result , isCacheHit } ;
282
+ } ) ;
277
283
}
278
284
279
285
private async suggestDiffCommandForCacheMiss ( req : CacheableChatRequest ) {
0 commit comments