.NET 8’s TimeProvider na marekebisho madogo ya SynchronizationContext yanaweza kupunguza sekunde nyingi kwenye majaribio ya kitengo (unit tests) ya mantiki ya kujaribu tena (retry-logic) ambayo vinginevyo hupoteza muda kwenye usingizi halisi (real sleeps). Jaribio ambalo zamani lilichukua sekunde saba sasa linamalizika ndani ya milisekunde chache, na mabadiliko hayo hayahitaji gharama yoyote ya ziada kuendesha kwenye uzalishaji (production).

Kwa nini ucheleweshaji ni muhimu

Wasaidizi wengi wa kujaribu tena (retry helpers) hutumia mbinu ya exponential backoff: subiri sekunde 1, kisha sekunde 2, kisha sekunde 4, na kuendelea. Kwenye uzalishaji, hiyo hulinda huduma zisipige (hammering) kiunganishi (endpoint) kinachofeli. Katika seti ya majaribio (test suite), kodi hiyo hiyo huiita Task.Delay kwenye saa halisi, hivyo thread ya jaribio inalala kweli. Zidisha hiyo kwa makumi ya majaribio na mchakato wa kuunganisha mfululizo (CI pipeline) utavurugika kwa dakika nyingi bila faida yoyote ya kiutendaji. "Kodi ya usingizi" (sleep tax) ni muda uliopotea bure.

Jinsi TimeProvider inavyofanya kazi

.NET 8 ilianzisha TimeProvider, ambalo ni muundo (abstraction) juu ya saa ya mfumo. Darasa (class) linalohitaji wakati wa sasa au linalohitaji kuchelewa linaweza kupokea mfano (instance) wa TimeProvider badala ya kuiita DateTime.UtcNow au Task.Delay moja kwa moja. Kwenye uzalishaji, unapita TimeProvider.System, ambayo huwasilisha kwenye saa halisi. Kwenye jaribio, unatoa FakeTimeProvider. Hiyo "fake" inakuwezesha kusogeza mbele wakati kwa mkono kwa kutumia Advance(TimeSpan). Ndani yake, Task.Delay husoma mtoa huduma (provider), hivyo kusogeza saa ya fake mbele kunakidhi mara moja ucheleweshaji wowote uliokuwa unasubiriwa.

Wazo ni rahisi: badilisha saa halisi na moja inayoweza kudhibitiwa, kisha ruka mbele hadi pale ambapo kodi inayojaribiwa ingekuwa imeendelea.

Mtego wa xUnit SynchronizationContext

Dhana hii inafanya kazi kwenye programu ya console, lakini kwenye xUnit ilisababisha deadlock. Jaribio liliita Advance() wakati msaidizi wa kujaribu tena ulikuwa unasubiri (awaiting) ucheleweshaji. xUnit huweka SynchronizationContext yake yenyewe ambayo hukamata mfululizo (continuations) na kuyaendesha kwenye thread ya jaribio. Wakati Advance() iliposogeza saa mbele, mfululizo huo uliwekwa kwenye thread pool badala ya thread ya jaribio. Thread ya jaribio iliendelea kusogeza mbele wakati, ikisukuma saa ya mfano kupita wakati ambapo jaribio lijalo linapaswa kuanza. Saa mpya iliwekwa kwa ajili ya wakati ujao ambao hautawahi kufikiwa kwa sababu hakuna thread iliyobaki kusogeza saa tena. Jaribio lilikwama bila mwisho.

Marekebisho ya mstari mmoja

Kuongeza mstari mmoja mwanzoni mwa jaribio hurudisha tabia inayotarajiwa:

SynchronizationContext.SetSynchronizationContext(null);

Kusafisha muktadha (context) maalum kunaulazimisha mfululizo wa await kuendesha kwenye thread pool, ambapo callbacks za saa ya fake zinaweza kutekelezwa. Kwa mabadiliko hayo, jaribio lile lile linashuka kutoka sekunde saba hadi takriban milisekunde 36.

Faida pana zaidi

Zaidi ya kuondoa usingizi usio na kazi, saa ya fake inafanya iwe rahisi kujaribu mantiki inayotegemea saa za eneo (time-zone-sensitive logic). Unaweza kuhakiki kwamba kiasi cha kila siku (daily quota) kinajifuta (resets) wakati wa usiku wa manane wa ndani (local midnight) kwa usahihi bila kusubiri saa halisi ifike saa sita. Njia hiyo hiyo inafanya kazi kwa kodi yoyote inayochagua njia kulingana na wakati wa sasa: ingiza mtoa huduma (provider), epuka wito uliofichika wa Thread.Sleep au Task.Delay, na utapata majaribio ya haraka na yanayotabirika (deterministic).

Vitu vya kuzingatia

  • Kila ucheleweshaji lazima upitishwe kupitia TimeProvider iliyowekwa. Thread.Sleep inayojitokeza au Task.Delay ya moja kwa moja bado itatumia saa halisi na kuleta tena ucheleweshaji.
  • Ikiwa maktaba (library) unayotegemea ndani yake huiita Task.Delay bila kuonyesha mtoa huduma, huwezi kudhibiti wakati wake bila kutumia mbinu ya ziada (invasive shim). Katika hali kama hizo, faida inaweza kuwa ndogo.
  • Tumia grep kwenye folda ya majaribio kutafuta Task.Delay na Thread.Sleep ili kubaini ucheleweshaji uliofichika. Kurekebisha (refactoring) wito huo ili utumie mtoa huduma ndiyo njia pekee ya kuifanya saa ya fake iendelee kuwa na ufanisi.

Hitimisho

Kubadilisha saa ya mfumo na TimeProvider na kuzima SynchronizationContext ya xUnit kunageuza majaribio ya kujaribu tena yaliyochelewa kuwa ukaguzi wa karibu papo hapo, jambo linalopunguza matumizi ya rasilimali za CI na kufanya kodi inayotegemea wakati iwe rahisi kuhakikiwa. Jitihada ni mistari michache ya kodi; faida yake inapimwa kwa sekunde zinazookolewa kwa kila mzunguko wa jaribio.