.NET 8 ರ TimeProvider ಮತ್ತು ಒಂದು SynchronizationContext ತಿದ್ದುಪಡಿ, ನೈಜ ನಿದ್ರೆಯ (real sleeps) ಕಾರಣದಿಂದ ನಿಷ್ಕ್ರಿಯವಾಗಿರುವ ರಿಟ್ರೈ-ಲಾಜಿಕ್ (retry-logic) ಯೂನಿಟ್ ಟೆಸ್ಟ್ಗಳಿಂದ ಸೆಕೆಂಡುಗಳನ್ನು ಉಳಿಸಬಹುದು. ಒಮ್ಮೆ ಏಳು ಸೆಕೆಂಡುಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳುತ್ತಿದ್ದ ಪರೀಕ್ಷೆಯು ಈಗ ಕೇವಲ ಕೆಲವು ಮಿಲಿಸೆಕೆಂಡುಗಳಲ್ಲಿ ಮುಕ್ತಾಯವಾಗುತ್ತದೆ ಮತ್ತು ಈ ಬದಲಾವಣೆಯು ಪ್ರೊಡಕ್ಷನ್ನಲ್ಲಿ ಯಾವುದೇ ಹೆಚ್ಚುವರಿ ವೆಚ್ಚವನ್ನು ಉಂಟುಮಾಡುವುದಿಲ್ಲ.
ವಿಳಂಬ ಏಕೆ ಮುಖ್ಯ
ಅನೇಕ ರಿಟ್ರೈ ಹೆಲ್ಪರ್ಗಳು (retry helpers) ಎಕ್ಸ್ಪೊನೆನ್ಶಿಯಲ್ ಬ್ಯಾಕಫ್ (exponential backoff) ಅನ್ನು ಬಳಸುತ್ತವೆ: 1 ಸೆಕೆಂಡ್ ಕಾಯಿರಿ, ನಂತರ 2 ಸೆಕೆಂಡ್, ನಂತರ 4 ಸೆಕೆಂಡ್, ಹೀಗೆ ಇತ್ಯಾದಿ. ಪ್ರೊಡಕ್ಷನ್ನಲ್ಲಿ, ಇದು ವಿಫಲವಾಗುತ್ತಿರುವ ಎಂಡ್ಪಾಯಿಂಟ್ಗೆ (endpoint) ಸತತವಾಗಿ ವಿನಂತಿಗಳನ್ನು ಕಳುಹಿಸುವುದರಿಂದ ಸೇವೆಗಳನ್ನು ರಕ್ಷಿಸುತ್ತದೆ. ಆದರೆ ಟೆಸ್ಟ್ ಸೂಟ್ನಲ್ಲಿ (test suite), ಅದೇ ಕೋಡ್ ನೈಜ ಗಡಿಯಾರದ ಮೇಲೆ Task.Delay ಅನ್ನು ಕರೆಯುತ್ತದೆ, ಆದ್ದರಿಂದ ಟೆಸ್ಟ್ ಥ್ರೆಡ್ (test thread) ನಿಜವಾಗಿಯೂ ನಿದ್ರಿಸುತ್ತದೆ. ಇದನ್ನು ಡಜನ್ಗಟ್ಟಲೆ ಟೆಸ್ಟ್ಗಳಿಗೆ ಗುಣಾಕಾರ ಮಾಡಿದರೆ, ಯಾವುದೇ ಕಾರ್ಯಕಾರಿ ಲಾಭವಿಲ್ಲದೆ ಕಂಟಿನ್ಯೂಯಸ್-ಇಂಟಿಗ್ರೇಷನ್ (CI) ಪೈಪ್ಲೈನ್ವು ನಿಮಿಷಗಟ್ಟಲೆ ವಿಳಂಬವಾಗುತ್ತದೆ. ಈ "ಸ್ಲೀಪ್ ಟ್ಯಾಕ್ಸ್" (sleep tax) ಸಂಪೂರ್ಣ ವ್ಯರ್ಥ ಸಮಯವಾಗಿದೆ.
TimeProvider ಹೇಗೆ ಕೆಲಸ ಮಾಡುತ್ತದೆ
.NET 8 ಸಿಸ್ಟಮ್ ಗಡಿಯಾರದ ಮೇಲಿನ ಅಬ್ಸ್ಟ್ರಾಕ್ಷನ್ (abstraction) ಆಗಿರುವ TimeProvider ಅನ್ನು ಪರಿಚಯಿಸಿತು. ಪ್ರಸ್ತುತ ಸಮಯದ ಅಗತ್ಯವಿರುವ ಅಥವಾ ವಿಳಂಬ ಮಾಡಬೇಕಾದ ಕ್ಲಾಸ್ (class), DateTime.UtcNow ಅಥವಾ Task.Delay ಅನ್ನು ನೇರವಾಗಿ ಕರೆಯುವ ಬದಲು TimeProvider ಇನ್ಸ್ಟೆನ್ಸ್ ಅನ್ನು ಸ್ವೀಕರಿಸಬಹುದು. ಪ್ರೊಡಕ್ಷನ್ನಲ್ಲಿ ನೀವು TimeProvider.System ಅನ್ನು ಬಳಸುತ್ತೀರಿ, ಇದು ನೈಜ ಗಡಿಯಾರಕ್ಕೆ ಕಳುಹಿಸುತ್ತದೆ. ಟೆಸ್ಟ್ನಲ್ಲಿ ನೀವು FakeTimeProvider ಅನ್ನು ನೀಡುತ್ತೀರಿ. ಈ ಫೇಕ್ ಪ್ರೊವೈಡರ್ Advance(TimeSpan) ಬಳಸಿ ಸಮಯವನ್ನು ಮ್ಯಾನುಯಲ್ ಆಗಿ ಮುನ್ನಡೆಸಲು ಅನುಮತಿಸುತ್ತದೆ. ಆಂತರಿಕವಾಗಿ Task.Delay ಪ್ರೊವೈಡರ್ ಅನ್ನು ಓದುತ್ತದೆ, ಆದ್ದರಿಂದ ಫೇಕ್ ಗಡಿಯಾರವನ್ನು ಮುನ್ನಡೆಸುವುದರಿಂದ ಬಾಕಿ ಇರುವ ಯಾವುದೇ ವಿಳಂಬವು ತಕ್ಷಣವೇ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ.
ಇದರ ಕಲ್ಪನೆ ಸರಳವಾಗಿದೆ: ನೈಜ ಗಡಿಯಾರದ ಬದಲಿಗೆ ನಿಯಂತ್ರಿಸಬಹುದಾದ ಗಡಿಯಾರವನ್ನು ಬಳಸಿ, ನಂತರ ಪರೀಕ್ಷಿಸಲಾಗುತ್ತಿರುವ ಕೋಡ್ ಎಲ್ಲಿಂದ ಪುನರಾರಂಭವಾಗುತ್ತದೆಯೋ ಆ ಸಮಯಕ್ಕೆ ನೇರವಾಗಿ ಜಿಗಿಯಿರಿ.
xUnit SynchronizationContext ಎಂಬ ಬಲೆ
ಈ ಪರಿಕಲ್ಪನೆಯು ಕನ್ಸೋಲ್ ಆಪ್ನಲ್ಲಿ (console app) ಕೆಲಸ ಮಾಡುತ್ತದೆ, ಆದರೆ xUnit ನಲ್ಲಿ ಇದು ডেಡ್ಲಾಕ್ (deadlock) ಉಂಟುಮಾಡಿತು. ರಿಟ್ರೈ ಹೆಲ್ಪರ್ ವಿಳಂಬಕ್ಕಾಗಿ ಕಾಯುತ್ತಿರುವಾಗ (awaiting a delay), ಟೆಸ್ಟ್ Advance() ಅನ್ನು ಕರೆದಿತು. xUnit ತನ್ನದೇ ಆದ SynchronizationContext ಅನ್ನು ಇನ್ಸ್ಟಾಲ್ ಮಾಡುತ್ತದೆ, ಇದು ಕಂಟಿನ್ಯೂಯೇಶನ್ಗಳನ್ನು (continuations) ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ ಟೆಸ್ಟ್ ಥ್ರೆಡ್ನಲ್ಲಿ ರನ್ ಮಾಡುತ್ತದೆ. Advance() ಗಡಿಯಾರವನ್ನು ಮುನ್ನಡೆಸಿದಾಗ, ಕಂಟಿನ್ಯೂಯೇಶನ್ ಟೆಸ್ಟ್ ಥ್ರೆಡ್ ಬದಲಿಗೆ ಥ್ರೆಡ್ ಪೂಲ್ಗೆ (thread pool) ಕ್ಯೂ ಮಾಡಲ್ಪಟ್ಟಿತು. ಟೆಸ್ಟ್ ಥ್ರೆಡ್ ಸಮಯವನ್ನು ಮುನ್ನಡೆಸುತ್ತಲೇ ಇತ್ತು, ಇದು ಸಿಮ್ಯುಲೇಟೆಡ್ ಗಡಿಯಾರವನ್ನು ಮುಂದಿನ ರಿಟ್ರೈ ನಡೆಯಬೇಕಾದ ಸಮಯಕ್ಕಿಂತಲೂ ಮುಂದೆ ತಳ್ಳಿತು. ಹೊಸ ಟೈಮರ್ ಭವಿಷ್ಯದ ಸಮಯಕ್ಕೆ ಸೆಟ್ ಮಾಡಲ್ಪಟ್ಟಿತು, ಆದರೆ ಗಡಿಯಾರವನ್ನು ಮತ್ತೆ ಮುನ್ನಡೆಸಲು ಯಾವುದೇ ಥ್ರೆಡ್ ಉಳಿದಿರದ ಕಾರಣ ಆ ಸಮಯವನ್ನು ತಲುಪಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಇದರಿಂದ ಟೆಸ್ಟ್ ಅನಿರ್ದಿಷ್ಟಕಾಲ ಸ್ಥಗಿತಗೊಂಡಿತು (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ಅನ್ನು ಕರೆಯುತ್ತಿದ್ದರೆ, ಹೆಚ್ಚು ತೀವ್ರವಾದ ಶಿಮ್ (shim) ಇಲ್ಲದೆ ನೀವು ಅದರ ಸಮಯವನ್ನು ನಿಯಂತ್ರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ಅಂತಹ ಸಂದರ್ಭಗಳಲ್ಲಿ ಪ್ರಯೋಜನವು ಸೀಮಿತವಾಗಿರಬಹುದು. - ಗುಪ್ತ ವಿಳಂಬಗಳನ್ನು ಪತ್ತೆಹಚ್ಚಲು ಟೆಸ್ಟ್ ಫೋಲ್ಡರ್ನಲ್ಲಿ
Task.Delayಮತ್ತುThread.Sleepಅನ್ನು ಗ್ರೆಪ್ (Grep) ಮಾಡಿ. ಫೇಕ್ ಗಡಿಯಾರವು ಪರಿಣಾಮಕಾರಿಯಾಗಿರಲು ಆ ಕರೆಗಳನ್ನು ಪ್ರೊವೈಡರ್ ಬಳಸುವಂತೆ ರಿಫ್ಯಾಕ್ಟರ್ (Refactoring) ಮಾಡುವುದು ಒಂದೇ ದಾರಿ.
ಸಾರಾಂಶ
ಸಿಸ್ಟಮ್ ಗಡಿಯಾರವನ್ನು TimeProvider ಮೂಲಕ ಬದಲಾಯಿಸುವುದು ಮತ್ತು xUnit ನ SynchronizationContext ಅನ್ನು ಅ禁用 (disable) ಮಾಡುವುದು ನಿಧಾನಗತಿಯ ರಿಟ್ರೈ ಟೆಸ್ಟ್ಗಳನ್ನು ತಕ್ಷಣವೇ ಪೂರ್ಣಗೊಳ್ಳುವ ಪರಿಶೀಲನೆಗಳನ್ನಾಗಿ ಬದಲಾಯಿಸುತ್ತದೆ, ಇದರಿಂದ CI ಸಂಪನ್ಮೂಲಗಳು ಉಳಿತಾಯವಾಗುತ್ತವೆ ಮತ್ತು ಸಮಯದ ಮೇಲೆ ಅವಲಂಬಿತವಾಗಿರುವ ಕೋಡ್ ಅನ್ನು ಪರಿಶೀಲಿಸುವುದು ಸುಲಭವಾಗುತ್ತದೆ. ಇದಕ್ಕೆ ಬೇಕಾಗುವ ಶ್ರಮ ಕೇವಲ ಕೆಲವು ಸಾಲುಗಳ ಕೋಡ್ ಮಾತ್ರ; ಆದರೆ ಪ್ರತಿ ಟೆಸ್ಟ್ ರನ್ನಲ್ಲಿ ಉಳಿತಾಯವಾಗುವ ಸೆಕೆಂಡುಗಳ ರೂಪದಲ್ಲಿ ಇದರ ಲಾಭ ಸಿಗುತ್ತದೆ.
