.NET 8 નું TimeProvider અને એકમાત્ર SynchronizationContext માં કરેલો ફેરફાર રીટ્રાય-લોજિક (retry-logic) યુનિટ ટેસ્ટમાંથી સેકન્ડો બચાવી શકે છે, જે અન્યથા રિયલ સ્લીપ્સ (real sleeps) ને કારણે નકામી રીતે સમય લેતા હોય છે. જે ટેસ્ટ પહેલા સાત સેકન્ડ લેતી હતી તે હવે માત્ર થોડા દસમા ભાગના મિલીસેકન્ડમાં પૂર્ણ થાય છે, અને આ ફેરફાર પ્રોડક્શનમાં ચલાવવા માટે કોઈ વધારાનો ખર્ચ કરતો નથી.

વિલંબ (delay) શા માટે મહત્વનો છે

ઘણા રીટ્રાય હેલ્પર્સ (retry helpers) એક્સપોનેન્શિયલ બેકઓફ (exponential backoff) નો ઉપયોગ કરે છે: 1 સેકન્ડ રાહ જુઓ, પછી 2 સેકન્ડ, પછી 4 સેકન્ડ, અને તે મુજબ. પ્રોડક્શનમાં, તે સર્વિસને નિષ્ફળ જતા એન્ડપોઇન્ટ પર સતત હુમલો કરતા બચાવે છે. ટેસ્ટ સ્યુટમાં, આ જ કોડ રિયલ ક્લોક પર Task.Delay ને કોલ કરે છે, તેથી ટેસ્ટ થ્રેડ ખરેખર ઊંઘી જાય છે (sleep). આને ડઝનબંધ ટેસ્ટ સાથે ગુણતા, કોઈ પણ ફંક્શનલ ફાયદા વગર કન્ટિન્યુઅસ-ઇન્ટિગ્રેશન (CI) પાઇપલાઇન મિનિટો સુધી લંબાઈ જાય છે. આ “સ્લીપ ટેક્સ” (sleep tax) એ માત્ર બિનજરૂરી સમયનો બગાડ છે.

TimeProvider કેવી રીતે કામ કરે છે

.NET 8 એ TimeProvider રજૂ કર્યું છે, જે સિસ્ટમ ક્લોક પરનું એક એબ્સ્ટ્રેક્શન (abstraction) છે. જે ક્લાસને વર્તમાન સમયની જરૂર હોય અથવા વિલંબ (delay) કરવાની જરૂર હોય તે DateTime.UtcNow અથવા Task.Delay ને સીધું કોલ કરવાને બદલે TimeProvider ઇન્સ્ટન્સ સ્વીકારી શકે છે. પ્રોડક્શનમાં તમે TimeProvider.System પાસ કરો છો, જે રિયલ ક્લોક પર ફોરવર્ડ કરે છે. ટેસ્ટમાં તમે FakeTimeProvider આપો છો. આ ફેક (fake) તમને Advance(TimeSpan) દ્વારા મેન્યુઅલી સમય આગળ વધારવાની મંજૂરી આપે છે. આંતરિક રીતે Task.Delay પ્રોવાઇડરને વાંચે છે, તેથી ફેક ક્લોકને આગળ વધારવાથી કોઈપણ પેન્ડિંગ ડિલે (pending delay) તરત જ પૂરો થઈ જાય છે.

વિચાર સરળ છે: રિયલ ક્લોકને કંટ્રોલેબલ (controllable) ક્લોક સાથે બદલો, અને પછી તે બિંદુ સુધી સીધા જ પહોંચી જાઓ જ્યાં ટેસ્ટ હેઠળનો કોડ ફરીથી શરૂ થયો હોત.

xUnit SynchronizationContext નો ફાંસો

આ ખ્યાલ કન્સોલ એપમાં કામ કરે છે, પરંતુ xUnit માં તેના કારણે ડેડલોક (deadlock) સર્જાયું હતું. જ્યારે રીટ્રાય હેલ્પર ડિલેની રાહ જોતું (awaiting) હતું ત્યારે ટેસ્ટે Advance() ને કોલ કર્યું હતું. xUnit પોતાનું SynchronizationContext ઇન્સ્ટોલ કરે છે જે કન્ટિന്യൂએશન્સ (continuations) ને કેપ્ચર કરે છે અને તેને ટેસ્ટ થ્રેડ પર ચલાવે છે. જ્યારે Advance() એ ક્લોકને આગળ વધારી, ત્યારે કન્ટિന്യൂએશન ટેસ્ટ થ્રેડને બદલે થ્રેડ પૂલમાં ક્યુ (queue) થઈ ગયું. ટેસ્ટ થ્રેડ સમયને આગળ વધારવાનું ચાલુ રાખતું રહ્યું, જેનાથી સિમ્યુલેટેડ ક્લોક તે ક્ષણથી પણ આગળ નીકળી ગઈ જ્યારે આગામી રીટ્રાય થવો જોઈતો હતો. નવો ટાઈમર ભવિષ્યના એવા બિંદુ માટે સેટ કરવામાં આવ્યો હતો જ્યાં ક્યારેય પહોંચી શકાશે નહીં કારણ કે ક્લોકને ફરીથી આગળ વધારવા માટે કોઈ થ્રેડ બાકી નહોતો. ટેસ્ટ અનિશ્ચિત સમય માટે અટકી (hung) ગયું.

એક-લાઇન ફિક્સ (fix)

ટેસ્ટની શરૂઆતમાં એક જ લાઇન ઉમેરવાથી અપેક્ષિત વર્તણૂક પુનઃસ્થાપિત થાય છે:

SynchronizationContext.SetSynchronizationContext(null);

કસ્ટમ કોન્ટેક્સ્ટ (custom context) ક્લિયર કરવાથી await કન્ટિന്യൂએશન્સ થ્રેડ પૂલ પર ચલાવવાની ફરજ પડે છે, જ્યાં ફેક ટાઈમરના કોલબેક્સ (callbacks) એક્ઝિક્યુટ થઈ શકે છે. આ ફેરફાર સાથે, તે જ ટેસ્ટ સાત સેકન્ડથી ઘટીને અંદાજે 36 મિલીસેકન્ડમાં પૂર્ણ થાય છે.

વ્યાપક ફાયદાઓ

નકામી ઊંઘ (idle sleeps) દૂર કરવા ઉપરાંત, ફેક ક્લોક ટાઈમ-ઝોન-સેન્સિટિવ (time-zone-sensitive) લોજિકનું ટેસ્ટિંગ સરળ બનાવે છે. તમે રિયલ ક્લોક બાર વાગવા સુધી રાહ જોયા વગર એ ચકાસી શકો છો કે દૈનિક ક્વોટા (daily quota) સાચા સ્થાનિક મધ્યરાત્રિએ રિસેટ થાય છે કે નહીં. આ જ અભિગમ કોઈપણ કોડ માટે કામ કરે છે જે વર્તમાન સમય પર આધારિત હોય: પ્રોવાઇડર ઇન્જેક્ટ કરો, Thread.Sleep અથવા Task.Delay ના છુપા કોલ્સ ટાળો, અને તમને ડિટરમિનિસ્ટિક (deterministic) અને ઝડપી ટેસ્ટ મળશે.

કઈ બાબતોનું ધ્યાન રાખવું

  • દરેક ડિલે (delay) ઇન્જેક્ટ કરેલા TimeProvider દ્વારા જ હોવી જોઈએ. કોઈ પણ વધારાનો Thread.Sleep અથવા સીધો Task.Delay હજુ પણ રિયલ ક્લોકને કોલ કરશે અને લેટન્સી (latency) ફરીથી લાવશે.
  • જો તમે જે લાઇબ્રેરી પર આધારિત છો તે પ્રોવાઇડર જાહેર કર્યા વિના આંતરિક રીતે Task.Delay ને કોલ કરે છે, તો તમે વધુ ઇન્વેઝિવ શિમ (invasive shim) વગર તેના સમયને નિયંત્રિત કરી શકતા નથી. આવા કિસ્સાઓમાં ફાયદો મર્યાદિત હોઈ શકે છે.
  • છુપા વેઇટ્સ (hidden waits) શોધવા માટે ટેસ્ટ ફોલ્ડરમાં Task.Delay અને Thread.Sleep માટે Grep કરો. ફેક ક્લોકને અસરકારક રાખવા માટે તે કોલ્સને પ્રોવાઇડરનો ઉપયોગ કરવા માટે રિફેક્ટર (refactor) કરવાનો જ એકમાત્ર રસ્તો છે.

સારાંશ

સિસ્ટમ ક્લોકને TimeProvider સાથે બદલવાથી અને xUnit ના SynchronizationContext ને ડિસેબલ કરવાથી ધીમા રીટ્રાય ટેસ્ટ લગભગ ત્વરિત ચેક (near-instant checks) માં બદલાઈ જાય છે, જેનાથી CI રિસોર્સિસ મુક્ત થાય છે અને સમય-આધારિત કોડને વેરિફાય કરવો સરળ બને છે. આ માટે માત્ર થોડી લાઇનના કોડની જરૂર છે; અને તેનો ફાયદો દરેક ટેસ્ટ રન દીઠ બચતી સેકન્ડોમાં માપી શકાય છે.