@@ -9,6 +9,9 @@ const { connectToRegistry } = require('./utils/rpc-client')
99const { parseCanonicalSource } = require ( '../lib/source-helpers' )
1010const QVACRegistryClient = require ( '../client/lib/client' )
1111
12+ const ADD_MODEL_RPC_TIMEOUT_MS = 60 * 60 * 1000
13+ const ADD_MODEL_POLL_INTERVAL_MS = 10 * 1000
14+
1215async function syncModels ( ) {
1316 const args = process . argv . slice ( 2 )
1417 const fileArg = args . find ( arg => arg . startsWith ( '--file=' ) )
@@ -30,7 +33,7 @@ async function syncModels () {
3033 const client = new QVACRegistryClient ( { registryCoreKey, logger } )
3134 await client . ready ( )
3235
33- const connection = await connectToRegistry ( { config, logger } )
36+ let connection = await connectToRegistry ( { config, logger } )
3437
3538 try {
3639 const configModels = JSON . parse ( await fs . readFile ( path . resolve ( filePath ) , 'utf8' ) )
@@ -95,7 +98,30 @@ async function syncModels () {
9598 modelRequest . deprecationReason = entry . deprecationReason
9699 }
97100
98- await connection . rpc . request ( 'add-model' , modelRequest )
101+ try {
102+ await connection . rpc . request ( 'add-model' , modelRequest , { timeout : ADD_MODEL_RPC_TIMEOUT_MS } )
103+ } catch ( err ) {
104+ if ( ! isAmbiguousRpcError ( err ) ) throw err
105+
106+ logger . warn ( {
107+ path : sourceInfo . path ,
108+ source : sourceInfo . protocol ,
109+ error : err . message ,
110+ code : err . code
111+ } , 'add-model RPC ended ambiguously; polling registry for completed ingest' )
112+
113+ const recovery = await recoverAfterAmbiguousAdd ( {
114+ client,
115+ sourceInfo,
116+ logger,
117+ connection,
118+ reconnect : ( ) => connectToRegistry ( { config, logger } )
119+ } )
120+ connection = recovery . connection
121+
122+ if ( recovery . error ) throw recovery . error
123+ dbByKey . set ( key , recovery . model )
124+ }
99125 } else {
100126 logger . info ( `[DRY RUN] Would add: ${ sourceInfo . path } ` )
101127 }
@@ -185,6 +211,88 @@ async function syncModels () {
185211 }
186212}
187213
214+ async function recoverAfterAmbiguousAdd ( {
215+ client,
216+ sourceInfo,
217+ logger,
218+ connection,
219+ reconnect,
220+ waitForModel = waitForModelAfterAmbiguousAdd
221+ } ) {
222+ let model = null
223+ let error = null
224+
225+ try {
226+ model = await waitForModel ( {
227+ client,
228+ sourceInfo,
229+ logger
230+ } )
231+ } catch ( err ) {
232+ error = err
233+ }
234+
235+ await connection . cleanup ( ) . catch ( cleanupErr => {
236+ logger . warn ( { error : cleanupErr . message } , 'Failed to clean up stale RPC connection' )
237+ } )
238+
239+ return {
240+ connection : await reconnect ( ) ,
241+ error,
242+ model
243+ }
244+ }
245+
246+ function isAmbiguousRpcError ( err ) {
247+ if ( ! err ) return false
248+
249+ const code = err . code || err . cause ?. code
250+ if ( code === 'ETIMEDOUT' || code === 'CHANNEL_CLOSED' || code === 'CHANNEL_DESTROYED' ) {
251+ return true
252+ }
253+
254+ const message = String ( err . message || '' ) . toLowerCase ( )
255+ return (
256+ message . includes ( 'connection timed out' ) ||
257+ message . includes ( 'channel closed' ) ||
258+ message . includes ( 'channel destroyed' )
259+ )
260+ }
261+
262+ async function waitForModelAfterAmbiguousAdd ( {
263+ client,
264+ sourceInfo,
265+ timeoutMs = ADD_MODEL_RPC_TIMEOUT_MS ,
266+ pollIntervalMs = ADD_MODEL_POLL_INTERVAL_MS ,
267+ logger = console ,
268+ sleep = defaultSleep
269+ } ) {
270+ const startedAt = Date . now ( )
271+
272+ while ( Date . now ( ) - startedAt <= timeoutMs ) {
273+ if ( client . db ?. core ?. update ) {
274+ await client . db . core . update ( )
275+ }
276+
277+ const model = await client . getModel ( sourceInfo . path , sourceInfo . protocol )
278+ if ( model ) {
279+ logger . info ( {
280+ path : sourceInfo . path ,
281+ source : sourceInfo . protocol
282+ } , 'Model appeared after ambiguous add-model RPC' )
283+ return model
284+ }
285+
286+ await sleep ( pollIntervalMs )
287+ }
288+
289+ throw new Error ( `Timed out waiting for model after ambiguous add-model RPC: ${ sourceInfo . path } ` )
290+ }
291+
292+ function defaultSleep ( ms ) {
293+ return new Promise ( resolve => setTimeout ( resolve , ms ) )
294+ }
295+
188296function needsMetadataUpdate ( config , existing , sourceInfo ) {
189297 return (
190298 config . engine !== existing . engine ||
@@ -274,4 +382,10 @@ if (require.main === module) {
274382 } )
275383}
276384
277- module . exports = { syncModels }
385+ module . exports = {
386+ ADD_MODEL_RPC_TIMEOUT_MS ,
387+ recoverAfterAmbiguousAdd,
388+ isAmbiguousRpcError,
389+ syncModels,
390+ waitForModelAfterAmbiguousAdd
391+ }
0 commit comments