@@ -165,6 +165,16 @@ private enum DisassemblerKind
165165 /// ツール毎の連続失敗回数と最終失敗時刻(協定世界時刻)。閾値超過で一定時間ブラックリスト化するために利用。
166166 /// </summary>
167167 private static readonly ConcurrentDictionary < string , ( int FailCount , DateTime LastFailUtc ) > _disassembleFailCountAndTime = new ( ) ;
168+
169+ /// <summary>
170+ /// 実行中に固定された逆アセンブラコマンド(混在防止)。
171+ /// </summary>
172+ private string _fixedDisassembleCommand ;
173+
174+ /// <summary>
175+ /// 逆アセンブラの初回選択を直列化するためのロック。
176+ /// </summary>
177+ private readonly SemaphoreSlim _disassemblerSelectionLock = new ( 1 , 1 ) ;
168178 #endregion
169179
170180 #region private writable member variables
@@ -215,44 +225,99 @@ public DotNetDisassembleService(ConfigSettings config, ILCache ilCache)
215225 /// <returns>逆アセンブル済み IL テキストと、人間が読めるコマンド表示(バージョン付き)をタプルで返します。</returns>
216226 public async Task < ( string ilText , string commandString ) > DisassembleAsync ( string dotNetAssemblyfileAbsolutePath )
217227 {
218- Exception lastError = null ;
228+ var fixedCommand = Volatile . Read ( ref _fixedDisassembleCommand ) ;
229+ if ( ! string . IsNullOrWhiteSpace ( fixedCommand ) )
230+ {
231+ return await DisassembleWithFixedCommandAsync ( fixedCommand , dotNetAssemblyfileAbsolutePath ) ;
232+ }
219233
220- foreach ( var candidateDisassembleCommand in CandidateDisassembleCommands ( ) )
234+ await _disassemblerSelectionLock . WaitAsync ( ) . ConfigureAwait ( false ) ;
235+ try
221236 {
222- // 直近で連続失敗したコマンドは一時的にブラックリスト化しているためスキップ。
223- if ( IsDisassemblerBlacklisted ( candidateDisassembleCommand ) )
237+ fixedCommand = _fixedDisassembleCommand ;
238+ if ( ! string . IsNullOrWhiteSpace ( fixedCommand ) )
224239 {
225- continue ;
240+ return await DisassembleWithFixedCommandAsync ( fixedCommand , dotNetAssemblyfileAbsolutePath ) ;
226241 }
227242
228- try
243+ Exception lastError = null ;
244+ foreach ( var candidateDisassembleCommand in CandidateDisassembleCommands ( ) )
229245 {
230- // キャッシュ確認とプロセス起動を内包した TryDisassembleAsync を実行。
231- var ( success , ilText , disassembleCommandAndItsVersionWithArguments , error ) = await TryDisassembleAsync ( candidateDisassembleCommand , dotNetAssemblyfileAbsolutePath ) ;
232- if ( success )
246+ // 直近で連続失敗したコマンドは一時的にブラックリスト化しているためスキップ。
247+ if ( IsDisassemblerBlacklisted ( candidateDisassembleCommand ) )
233248 {
234- return ( ilText , disassembleCommandAndItsVersionWithArguments ) ;
249+ continue ;
235250 }
236- if ( error != null )
251+
252+ try
237253 {
238- lastError = error ;
254+ // キャッシュ確認とプロセス起動を内包した TryDisassembleAsync を実行。
255+ var ( success , ilText , disassembleCommandAndItsVersionWithArguments , error ) = await TryDisassembleAsync ( candidateDisassembleCommand , dotNetAssemblyfileAbsolutePath , allowCache : false ) ;
256+ if ( success )
257+ {
258+ Volatile . Write ( ref _fixedDisassembleCommand , candidateDisassembleCommand ) ;
259+ return ( ilText , disassembleCommandAndItsVersionWithArguments ) ;
260+ }
261+ if ( error != null )
262+ {
263+ lastError = error ;
264+ }
265+ }
266+ catch ( System . ComponentModel . Win32Exception ex )
267+ {
268+ lastError = ex ;
269+ LoggerService . LogMessage ( LoggerService . LogLevel . Warning , string . Format ( LOG_FAILED_TO_START_DISASSEMBLER , candidateDisassembleCommand , ex . Message ) , shouldOutputMessageToConsole : true , ex ) ;
270+ RegisterDisassembleFailure ( candidateDisassembleCommand ) ;
271+ continue ;
272+ }
273+ catch ( Exception ex )
274+ {
275+ lastError = ex ;
276+ LoggerService . LogMessage ( LoggerService . LogLevel . Warning , string . Format ( LOG_UNEXPECTED_ERROR_PREPARING_DISASSEMBLER , candidateDisassembleCommand , ex . Message ) , shouldOutputMessageToConsole : true , ex ) ;
277+ RegisterDisassembleFailure ( candidateDisassembleCommand ) ;
278+ continue ;
239279 }
240280 }
241- catch ( System . ComponentModel . Win32Exception ex )
281+
282+ var innerMsg = lastError != null ? string . Format ( INFO_ROOT_CAUSE_FORMAT , lastError . Message ) : string . Empty ;
283+ throw new InvalidOperationException ( string . Format ( ERROR_EXECUTE_ILDASM , dotNetAssemblyfileAbsolutePath , GUIDANCE_INSTALL_DISASSEMBLER , innerMsg ) , lastError ) ;
284+ }
285+ finally
286+ {
287+ _disassemblerSelectionLock . Release ( ) ;
288+ }
289+ }
290+
291+ /// <summary>
292+ /// 固定済みの逆アセンブラのみで実行します(混在防止)。
293+ /// </summary>
294+ private async Task < ( string ilText , string commandString ) > DisassembleWithFixedCommandAsync ( string disassembleCommand , string dotNetAssemblyfileAbsolutePath )
295+ {
296+ Exception lastError = null ;
297+ try
298+ {
299+ var ( success , ilText , disassembleCommandAndItsVersionWithArguments , error ) = await TryDisassembleAsync ( disassembleCommand , dotNetAssemblyfileAbsolutePath , allowCache : true ) ;
300+ if ( success )
242301 {
243- lastError = ex ;
244- LoggerService . LogMessage ( LoggerService . LogLevel . Warning , string . Format ( LOG_FAILED_TO_START_DISASSEMBLER , candidateDisassembleCommand , ex . Message ) , shouldOutputMessageToConsole : true , ex ) ;
245- RegisterDisassembleFailure ( candidateDisassembleCommand ) ;
246- continue ;
302+ return ( ilText , disassembleCommandAndItsVersionWithArguments ) ;
247303 }
248- catch ( Exception ex )
304+ if ( error != null )
249305 {
250- lastError = ex ;
251- LoggerService . LogMessage ( LoggerService . LogLevel . Warning , string . Format ( LOG_UNEXPECTED_ERROR_PREPARING_DISASSEMBLER , candidateDisassembleCommand , ex . Message ) , shouldOutputMessageToConsole : true , ex ) ;
252- RegisterDisassembleFailure ( candidateDisassembleCommand ) ;
253- continue ;
306+ lastError = error ;
254307 }
255308 }
309+ catch ( System . ComponentModel . Win32Exception ex )
310+ {
311+ lastError = ex ;
312+ LoggerService . LogMessage ( LoggerService . LogLevel . Warning , string . Format ( LOG_FAILED_TO_START_DISASSEMBLER , disassembleCommand , ex . Message ) , shouldOutputMessageToConsole : true , ex ) ;
313+ RegisterDisassembleFailure ( disassembleCommand ) ;
314+ }
315+ catch ( Exception ex )
316+ {
317+ lastError = ex ;
318+ LoggerService . LogMessage ( LoggerService . LogLevel . Warning , string . Format ( LOG_UNEXPECTED_ERROR_PREPARING_DISASSEMBLER , disassembleCommand , ex . Message ) , shouldOutputMessageToConsole : true , ex ) ;
319+ RegisterDisassembleFailure ( disassembleCommand ) ;
320+ }
256321
257322 var innerMsg = lastError != null ? string . Format ( INFO_ROOT_CAUSE_FORMAT , lastError . Message ) : string . Empty ;
258323 throw new InvalidOperationException ( string . Format ( ERROR_EXECUTE_ILDASM , dotNetAssemblyfileAbsolutePath , GUIDANCE_INSTALL_DISASSEMBLER , innerMsg ) , lastError ) ;
@@ -398,7 +463,10 @@ public async Task PrefetchIlCacheAsync(IEnumerable<string> dotNetAssemblyFilesAb
398463 /// <param name="disassembleCommand">使用するコマンド(ildasm / dotnet / ilspycmd など)</param>
399464 /// <param name="dotNetAssemblyFileAbsolutePath">対象アセンブリの絶対パス</param>
400465 /// <returns>成功可否、IL テキスト、ツールラベル、発生した例外</returns>
401- private async Task < ( bool Success , string IlText , string DisassembleCommandAndItsVersionWithArguments , Exception Error ) > TryDisassembleAsync ( string disassembleCommand , string dotNetAssemblyFileAbsolutePath )
466+ private async Task < ( bool Success , string IlText , string DisassembleCommandAndItsVersionWithArguments , Exception Error ) > TryDisassembleAsync (
467+ string disassembleCommand ,
468+ string dotNetAssemblyFileAbsolutePath ,
469+ bool allowCache )
402470 {
403471 Exception lastError = null ;
404472 string tempAsciiPath = CreateAsciiTempCopyIfNeeded ( dotNetAssemblyFileAbsolutePath ) ;
@@ -407,7 +475,7 @@ public async Task PrefetchIlCacheAsync(IEnumerable<string> dotNetAssemblyFilesAb
407475 {
408476 foreach ( var argset in BuildArgSets ( disassembleCommand , dotNetAssemblyFileAbsolutePath , tempAsciiPath ) )
409477 {
410- var ( success , ilText , disassembleCommandAndItsVersionWithArguments , error ) = await TryDisassembleWithArguments ( disassembleCommand , dotNetAssemblyFileAbsolutePath , argset ) ;
478+ var ( success , ilText , disassembleCommandAndItsVersionWithArguments , error ) = await TryDisassembleWithArguments ( disassembleCommand , dotNetAssemblyFileAbsolutePath , argset , allowCache ) ;
411479 if ( success )
412480 {
413481 return ( success , ilText , disassembleCommandAndItsVersionWithArguments , error ) ;
@@ -435,13 +503,14 @@ public async Task PrefetchIlCacheAsync(IEnumerable<string> dotNetAssemblyFilesAb
435503 private async Task < ( bool Success , string IlText , string DisassembleCommandAndItsVersionWithArguments , Exception Error ) > TryDisassembleWithArguments (
436504 string disassembleCommand ,
437505 string dotNetAssemblyFileAbsolutePath ,
438- ( string workingDirectory , string [ ] args , string tempOut ) argset )
506+ ( string workingDirectory , string [ ] args , string tempOut ) argset ,
507+ bool allowCache )
439508 {
440509 string disassembleCommandAndItsVersionWithArguments = null ;
441510 string ilText = null ;
442511
443512 // 逆アセンブル結果の取得前に IL キャッシュを確認してヒットすればプロセス起動を省略する。
444- if ( _config . EnableILCache && _ilCache != null )
513+ if ( allowCache && _config . EnableILCache && _ilCache != null )
445514 {
446515 try
447516 {
0 commit comments