.NET 8-ன் TimeProvider மற்றும் ஒரு சிறிய SynchronizationContext மாற்றம், உண்மையான 'sleep' நேரங்களால் தாமதமாகும் retry-logic unit test-களின் நேரத்தை பல வினாடிகள் குறைக்க உதவும். ஒருமுறை ஏழு வினாடிகள் எடுத்த சோதனை, இப்போது சில மில்லி விநாடிகளில் முடிந்துவிடும்; மேலும் இந்த மாற்றத்தால் production சூழலில் கூடுதல் செலவோ அல்லது பாதிப்போ ஏற்படாது.

தாமதம் ஏன் முக்கியமானது

பல retry helpers 'exponential backoff' முறையைப் பயன்படுத்துகின்றன: முதலில் 1 வினாடி, பிறகு 2 வினாடிகள், பிறகு 4 வினாடிகள் என காத்திருக்கும். Production சூழலில், இது தோல்வியடையும் ஒரு endpoint-ஐத் தொடர்ந்து தாக்கிச் சேவைகளைப் பாதிக்காமல் தடுக்கிறது. ஆனால் ஒரு test suite-ல், அதே குறியீடு உண்மையான கடிகாரத்தைப் பயன்படுத்தி Task.Delay-ஐ அழைப்பதால், test thread உண்மையில் தூங்குகிறது (sleep). இதை டஜன் கணக்கான சோதனைகளுடன் பெருக்கினால், எந்தவொரு செயல்பாட்டுப் பயனும் இன்றி continuous-integration (CI) pipeline பல நிமிடங்கள் நீடிக்கிறது. இந்த “sleep tax” என்பது முற்றிலும் வீணாகும் நேரமாகும்.

TimeProvider எவ்வாறு செயல்படுகிறது

.NET 8, system clock-க்கான ஒரு abstraction ஆக TimeProvider-ஐ அறிமுகப்படுத்தியது. தற்போதைய நேரம் தேவைப்படும் அல்லது தாமதப்படுத்த வேண்டிய ஒரு class, DateTime.UtcNow அல்லது Task.Delay-ஐ நேரடியாக அழைப்பதற்குப் பதிலாக, ஒரு TimeProvider instance-ஐப் பெற்றுக்கொள்ளலாம். Production சூழலில், நீங்கள் TimeProvider.System-ஐக் கடத்துவீர்கள், இது உண்மையான கடிகாரத்திற்குச் செல்லும். ஒரு test-ல், நீங்கள் ஒரு FakeTimeProvider-ஐ வழங்குவீர்கள். இந்த 'fake' மூலம் Advance(TimeSpan) பயன்படுத்தி நேரத்தை நீங்கள் கைமுறையாக நகர்த்த முடியும். உள்ளுக்குள் Task.Delay இந்த provider-ஐப் படிப்பதால், fake கடிகாரத்தை முன்னோக்கி நகர்த்துவது நிலுவையில் உள்ள எந்தவொரு தாமதத்தையும் உடனடியாக நிறைவு செய்துவிடும்.

இதன் கருத்து எளிமையானது: உண்மையான கடிகாரத்திற்குப் பதிலாகக் கட்டுப்படுத்தக்கூடிய ஒரு கடிகாரத்தை மாற்றுவது, பின்னர் சோதனை செய்யப்படும் குறியீடு மீண்டும் தொடங்கும் இடத்திற்கு நேரத்தை முன்னோக்கித் தாவுவது.

xUnit SynchronizationContext சிக்கல்

இந்தத் தத்துவம் ஒரு console app-ல் வேலை செய்யும், ஆனால் xUnit-ல் இது ஒரு deadlock-ஐ (முட்டுக்கட்டை) ஏற்படுத்தியது. retry helper ஒரு தாமதத்திற்காகக் காத்திருக்கும் (awaiting) போது, test Advance()-ஐ அழைத்தது. xUnit அதன் சொந்த SynchronizationContext-ஐ நிறுவி, continuations-களைப் பிடித்து அவற்றை test thread-ல் இயக்குகிறது. Advance() கடிகாரத்தை முன்னோக்கி நகர்த்திய போது, continuation test thread-க்கு பதிலாக thread pool-க்கு வரிசைப்படுத்தப்பட்டது. test thread நேரத்தைத் தொடர்ந்து நகர்த்திக்கொண்டே இருந்தது, இதனால் அடுத்த retry எப்போது நடக்க வேண்டுமோ அந்த நேரத்தைத் தாண்டிச் செயற்கைக் கடிகாரம் சென்றுவிட்டது. கடிகாரத்தை மீண்டும் நகர்த்த எந்த thread-உம் இல்லாததால், புதிய timer ஒருபோதும் அடைய முடியாத எதிர்காலப் புள்ளியில் அமைந்தது. இதனால் test முடிவில்லாமல் அப்படியே நின்றுவிட்டது.

ஒரு வரியில் தீர்வு

சோதனையின் தொடக்கத்தில் ஒரு வரியைச் சேர்ப்பதன் மூலம் எதிர்பார்த்த செயல்பாட்டை மீட்டெடுக்கலாம்:

SynchronizationContext.SetSynchronizationContext(null);

Custom context-ஐ நீக்குவது, await continuations-களை thread pool-ல் இயங்கத் தூண்டுகிறது, அங்கு fake timer-ன் callbacks-களை இயக்க முடியும். அந்த மாற்றத்துடன், அதே சோதனை ஏழு வினாடிகளிலிருந்து சுமார் 36 மில்லி விநாடிகளாகக் குறைகிறது.

விரிவான நன்மைகள்

தேவையற்ற sleep-களைத் தவிர்ப்பதுடன் மட்டுமல்லாமல், ஒரு fake clock மூலம் time-zone சார்ந்த தர்க்கங்களை (logic) எளிதாகச் சோதனை செய்யலாம். உண்மையான கடிகாரம் பன்னிரண்டு மணி அடிக்கக் காத்திருக்காமல், ஒரு தினசரி ஒதுக்கீடு (daily quota) சரியான உள்ளூர் நள்ளிரவில் மீட்டமைக்கப்படுகிறதா என்பதை நீங்கள் சரிபார்க்க முடியும். தற்போதைய நேரத்தைப் பொறுத்து செயல்படும் எந்தவொரு குறியீட்டிற்கும் இதே அணுகுமுறை பொருந்தும்: provider-ஐப் பயன்படுத்துங்கள், Thread.Sleep அல்லது Task.Delay-ன் மறைமுக அழைப்புகளைத் தவிர்க்கவும், இதன் மூலம் நீங்கள் துல்லியமான (deterministic) மற்றும் வேகமான சோதனைகளைப் பெறலாம்.

கவனிக்க வேண்டியவை

  • ஒவ்வொரு தாமதமும் (delay) செலுத்தப்பட்ட (injected) TimeProvider-ன் வழியாகவே செல்ல வேண்டும். ஒரு stray Thread.Sleep அல்லது நேரடி Task.Delay இன்னும் உண்மையான கடிகாரத்தையே அழைக்கும், இதனால் மீண்டும் தாமதம் ஏற்படும்.
  • நீங்கள் சார்ந்திருக்கும் ஒரு library, provider-ஐ வெளிப்படுத்தாமல் உள்ளுக்குள் Task.Delay-ஐ அழைத்தால், ஒரு சிக்கலான shim இல்லாமல் அதன் நேரத்தைக் கட்டுப்படுத்த முடியாது. இத்தகைய சூழல்களில் இதன் பயன் குறைவாக இருக்கலாம்.
  • மறைமுகமான காத்திருப்புகளைக் கண்டறிய test folder-ல் Task.Delay மற்றும் Thread.Sleep ஆகியவற்றை grep மூலம் தேடுங்கள். fake clock பயனுள்ளதாக இருக்க, அந்த அழைப்புகளை provider-ஐப் பயன்படுத்தும் வகையில் மாற்றுவதே (refactoring) ஒரே வழி.

சுருக்கம்

system clock-க்கு பதிலாக TimeProvider-ஐப் பயன்படுத்துவதும், xUnit-ன் SynchronizationContext-ஐ முடக்குவதும் மந்தமான retry test-களை உடனடிச் சோதனைகளாக மாற்றுகிறது; இது CI வளங்களைச் சேமிப்பதோடு, நேரத்தைச் சார்ந்த குறியீடுகளைச் சரிபார்ப்பதையும் எளிதாக்குகிறது. இதற்குத் தேவை சில வரிகள் குறியீடு மட்டுமே; ஆனால் அதன் பலன் ஒவ்வொரு சோதனை ஓட்டத்திலும் சேமிக்கப்படும் வினாடிகளாக இருக்கும்.