.NET 8 యొక్క TimeProvider మరియు ఒక చిన్న SynchronizationContext మార్పు ద్వారా, రిట్రై-లాజిక్ (retry-logic) యూనిట్ టెస్టులలో అనవసరంగా సమయం వృథా అయ్యేలా చేసే 'real sleeps' ని నివారించి, సెకన్ల సమయాన్ని ఆదా చేయవచ్చు. ఒకప్పుడు ఏడు సెకన్లు పట్టే టెస్ట్, ఇప్పుడు కేవలం కొన్ని మిల్లీసెకన్లలోనే పూర్తవుతుంది; పైగా ఈ మార్పు ప్రొడక్షన్‌లో ఎటువంటి అదనపు ఖర్చు లేకుండా పనిచేస్తుంది.

ఆలస్యం ఎందుకు సమస్య అవుతుంది

చాలా రిట్రై హెల్పర్ల (retry helpers) లో ఎక్స్‌పోనెన్షియల్ బ్యాకoff (exponential backoff) విధానాన్ని ఉపయోగిస్తారు: అంటే మొదట 1 సెకను, తర్వాత 2 సెకన్లు, ఆపై 4 సెకన్లు ఇలా వేచి ఉండటం. ప్రొడక్షన్‌లో, ఇది ఫెయిల్ అవుతున్న ఎండ్‌పాయింట్‌పై (endpoint) సర్వీసుల ఒత్తిడిని తగ్గించడానికి ఉపయోగపడుతుంది. కానీ టెస్ట్ సూట్‌లో, అదే కోడ్ రియల్ క్లాక్ (real clock) పై Task.Delayని పిలుస్తుంది, దీనివల్ల టెస్ట్ థ్రెడ్ నిజంగానే నిద్రపోతుంది (sleep). ఇలాంటి డజన్ల కొద్దీ టెస్టులు ఉన్నప్పుడు, ఎటువంటి ప్రయోజనం లేకుండానే కంటిన్యూయస్-ఇంటిగ్రేషన్ (CI) పైప్‌లైన్ నిమిషాల కొద్దీ ఆలస్యమవుతుంది. ఈ “sleep tax” అనేది కేవలం వృథా సమయం మాత్రమే.

TimeProvider ఎలా పనిచేస్తుంది

.NET 8 సిస్టమ్ క్లాక్ పైన ఒక అబ్‌స్ట్రాక్షన్‌గా (abstraction) TimeProviderని పరిచయం చేసింది. ప్రస్తుత సమయం కావాల్సిన లేదా ఆలస్యం (delay) అవసరమైన క్లాస్, నేరుగా DateTime.UtcNow లేదా Task.Delayని పిలవడానికి బదులుగా TimeProvider ఇన్‌స్టెన్స్‌ను స్వీకరించవచ్చు. ప్రొడక్షన్‌లో మీరు TimeProvider.Systemని ఉపయోగిస్తారు, ఇది రియల్ క్లాక్‌కి పంపిస్తుంది. టెస్టింగ్‌లో మీరు FakeTimeProviderని అందిస్తారు. ఈ ఫేక్ ప్రొవైడర్ ద్వారా Advance(TimeSpan) ఉపయోగించి సమయాన్ని మాన్యువల్‌గా ముందుకు జరపవచ్చు. అంతర్గతంగా Task.Delay ఈ ప్రొవైడర్‌ను చదువుతుంది, కాబట్టి ఫేక్ క్లాక్‌ను ముందుకు జరపడం ద్వారా పెండింగ్‌లో ఉన్న ఏ ఆలస్యాన్నైనా వెంటనే పూర్తి చేయవచ్చు.

దీని వెనుక ఉన్న ఆలోచన సరళమైనది: రియల్ క్లాక్‌ను నియంత్రించగలిగే క్లాక్‌తో మార్చడం, ఆపై టెస్ట్ చేయబడుతున్న కోడ్ మళ్ళీ ఎక్కడ మొదలవుతుందో ఆ సమయానికి నేరుగా వెళ్లడం.

xUnit SynchronizationContext ట్రాప్

ఈ కాన్సెప్ట్ కన్సోల్ యాప్‌లో బాగా పనిచేస్తుంది, కానీ xUnitలో ఇది డెడ్‌లాక్ (deadlock) కు దారితీసింది. రిట్రై హెల్పర్ ఒక ఆలస్యం (delay) కోసం వేచి ఉన్నప్పుడు, టెస్ట్ Advance()ని పిలిచింది. xUnit తన స్వంత SynchronizationContextని ఇన్‌స్టాల్ చేస్తుంది, ఇది కంటిన్యూయేషన్లను (continuations) క్యాప్చర్ చేసి టెస్ట్ థ్రెడ్‌పై రన్ చేస్తుంది. Advance() క్లాక్‌ను ముందుకు జరిపినప్పుడు, కంటిన్యూయేషన్ టెస్ట్ థ్రెడ్‌కు బదులుగా థ్రెడ్ పూల్‌కు క్యూ చేయబడింది. టెస్ట్ థ్రెడ్ సమయాన్ని ముందుకు జరుపుతూనే ఉంది, దీనివల్ల సిమ్యులేటెడ్ క్లాక్ తదుపరి రిట్రై జరగాల్సిన సమయం కంటే ముందుకు వెళ్ళిపోయింది. కొత్త టైమర్ భవిష్యత్తులో ఒక సమయానికి సెట్ చేయబడింది, కానీ క్లాక్‌ను మళ్ళీ ముందుకు జరపడానికి ఎటువంటి థ్రెడ్ మిగలకపోవడంతో ఆ సమయం ఎప్పటికీ రాదు. దీనివల్ల టెస్ట్ అనంతంగా ఆగిపోయింది (hung indefinitely).

ఒకే లైన్ పరిష్కారం

టెస్ట్ ప్రారంభంలో ఒకే ఒక లైన్ జోడించడం ద్వారా ఆశించిన ప్రవర్తనను తిరిగి పొందవచ్చు:

SynchronizationContext.SetSynchronizationContext(null);

కస్టమ్ కాంటెక్స్ట్‌ను క్లియర్ చేయడం వల్ల await కంటిన్యూయేషన్లు థ్రెడ్ పూల్‌లో రన్ అవుతాయి, అక్కడ ఫేక్ టైమర్ యొక్క కాల్‌బ్యాక్‌లు (callbacks) అమలు కాగలవు. ఈ మార్పుతో, అదే టెస్ట్ ఏడు సెకన్ల నుండి సుమారు 36 మిల్లీసెకన్లకు తగ్గిపోయింది.

విస్తృత ప్రయోజనాలు

అనవసరమైన స్లీప్‌లను (idle sleeps) తొలగించడమే కాకుండా, ఫేక్ క్లాక్ ద్వారా టైమ్-జోన్-సెన్సిటివ్ (time-zone-sensitive) లాజిక్‌ను టెస్ట్ చేయడం సులభం అవుతుంది. రియల్ క్లాక్ పన్నెండు గంటలు అయ్యే వరకు వేచి ఉండకుండానే, రోజువారీ కోటా సరైన స్థానిక అర్ధరాత్రి సమయానికి రీసెట్ అవుతుందో లేదో మీరు ధృవీకరించవచ్చు. ప్రస్తుత సమయం ఆధారంగా పనిచేసే ఏ కోడ్‌కైనా ఇదే విధానం వర్తిస్తుంది: ప్రొవైడర్‌ను ఇంజెక్ట్ చేయండి, Thread.Sleep లేదా Task.Delay వంటి దాగి ఉన్న కాల్స్‌ను నివారించండి, తద్వారా మీరు ఖచ్చితమైన (deterministic) మరియు వేగవంతమైన టెస్టులను పొందవచ్చు.

గమనించవలసినవి

  • ప్రతి ఆలస్యం (delay) ఇంజెక్ట్ చేయబడిన TimeProvider ద్వారానే జరగాలి. ఏదైనా పొరపాటున Thread.Sleep లేదా నేరుగా Task.Delayని ఉపయోగిస్తే, అది మళ్ళీ రియల్ క్లాక్‌ను పిలిచి ఆలస్యాన్ని కలిగిస్తుంది.
  • మీరు ఉపయోగిస్తున్న లైబ్రరీ ప్రొవైడర్‌ను బయట పెట్టకుండా అంతర్గతంగా Task.Delayని ఉపయోగిస్తుంటే, మరింత లోతైన మార్పులు (invasive shim) చేయకుండా దాని టైమింగ్‌ను మీరు నియంత్రించలేరు. అటువంటి సందర్భాల్లో ప్రయోజనం పరిమితంగా ఉండవచ్చు.
  • దాగి ఉన్న వెయిట్‌లను (hidden waits) గుర్తించడానికి టెస్ట్ ఫోల్డర్‌లో Task.Delay మరియు Thread.Sleep కోసం గ్రేప్ (Grep) చేయండి. ఫేక్ క్లాక్ సమర్థవంతంగా పనిచేయాలంటే ఆ కాల్స్‌ను ప్రొవైడర్‌ను ఉపయోగించేలా రీఫ్యాక్టర్ (Refactoring) చేయడం ఒక్కటే మార్గం.

ముగింపు

సిస్టమ్ క్లాక్‌ను TimeProviderతో మార్చడం మరియు xUnit యొక్క SynchronizationContextని నిలిపివేయడం వల్ల నెమ్మదిగా ఉండే రిట్రై టెస్టులు తక్షణమే పూర్తయ్యే చెక్‌లుగా మారుతాయి. ఇది CI వనరులను ఆదా చేయడమే కాకుండా, సమయంపై ఆధారపడిన కోడ్‌ను ధృవీకరించడాన్ని సులభతరం చేస్తుంది. దీని కోసం కేవలం కొన్ని లైన్ల కోడ్ మాత్రమే అవసరం; కానీ ప్రతి టెస్ట్ రన్‌లో ఆదా అయ్యే సెకన్ల సమయం దీని వల్ల కలిగే పెద్ద లాభం.