< Summary

Information
Class: Ice.Internal.RetryTask
Assembly: Ice
File(s): /_/csharp/src/Ice/Internal/RetryQueue.cs
Tag: 125_37167941578
Line coverage
78%
Covered lines: 18
Uncovered lines: 5
Coverable lines: 23
Total lines: 149
Line coverage: 78.2%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage
75%
Covered methods: 3
Fully covered methods: 3
Total methods: 4
Method coverage: 75%
Full method coverage: 75%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
runTimerTask()100%11100%
asyncRequestCanceled(...)100%66100%
destroy()100%210%

File(s)

/_/csharp/src/Ice/Internal/RetryQueue.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Diagnostics;
 4
 5namespace Ice.Internal;
 6
 7public class RetryTask : TimerTask, CancellationHandler
 8{
 19    public RetryTask(Instance instance, RetryQueue retryQueue, ProxyOutgoingAsyncBase outAsync)
 10    {
 111        _instance = instance;
 112        _retryQueue = retryQueue;
 113        _outAsync = outAsync;
 114    }
 15
 16    public void runTimerTask()
 17    {
 18        try
 19        {
 120            _outAsync.retry();
 121        }
 22        finally
 23        {
 24            //
 25            // NOTE: this must be called last, destroy() blocks until all task
 26            // are removed to prevent the client thread pool to be destroyed
 27            // (we still need the client thread pool at this point to call
 28            // exception callbacks with CommunicatorDestroyedException).
 29            //
 130            _retryQueue.remove(this);
 131        }
 132    }
 33
 34    public void asyncRequestCanceled(OutgoingAsyncBase outAsync, Ice.LocalException ex)
 35    {
 36        Debug.Assert(_outAsync == outAsync);
 137        if (_retryQueue.cancel(this))
 38        {
 139            if (_instance.traceLevels().retry >= 1)
 40            {
 141                _instance.initializationData().logger.trace(
 142                    _instance.traceLevels().retryCat,
 143                    $"operation retry canceled\n{ex}");
 44            }
 145            if (_outAsync.exception(ex))
 46            {
 147                _outAsync.invokeExceptionAsync();
 48            }
 49        }
 150    }
 51
 52    public void destroy()
 53    {
 54        try
 55        {
 056            _outAsync.abort(new Ice.CommunicatorDestroyedException());
 057        }
 058        catch (Ice.CommunicatorDestroyedException)
 59        {
 60            // Abort can throw if there's no callback, just ignore in this case
 061        }
 062    }
 63
 64    private readonly Instance _instance;
 65    private readonly RetryQueue _retryQueue;
 66    private readonly ProxyOutgoingAsyncBase _outAsync;
 67}
 68
 69public class RetryQueue
 70{
 71    public RetryQueue(Instance instance) => _instance = instance;
 72
 73    public void add(ProxyOutgoingAsyncBase outAsync, int interval)
 74    {
 75        Debug.Assert(interval >= 0);
 76        lock (_mutex)
 77        {
 78            if (_instance == null)
 79            {
 80                throw new Ice.CommunicatorDestroyedException();
 81            }
 82            var task = new RetryTask(_instance, this, outAsync);
 83            outAsync.cancelable(task); // This will throw if the request is canceled.
 84            _instance.timer().schedule(task, TimeSpan.FromMilliseconds(interval));
 85            _requests.Add(task, null);
 86        }
 87    }
 88
 89    public void destroy()
 90    {
 91        lock (_mutex)
 92        {
 93            var keep = new Dictionary<RetryTask, object>();
 94            foreach (RetryTask task in _requests.Keys)
 95            {
 96                if (_instance.timer().cancel(task))
 97                {
 98                    task.destroy();
 99                }
 100                else
 101                {
 102                    keep.Add(task, null);
 103                }
 104            }
 105            _requests = keep;
 106            _instance = null;
 107            while (_requests.Count > 0)
 108            {
 109                System.Threading.Monitor.Wait(_mutex);
 110            }
 111        }
 112    }
 113
 114    public void remove(RetryTask task)
 115    {
 116        lock (_mutex)
 117        {
 118            if (_requests.Remove(task))
 119            {
 120                if (_instance == null && _requests.Count == 0)
 121                {
 122                    // If we are destroying the queue, destroy is probably waiting on the queue to be empty.
 123                    System.Threading.Monitor.Pulse(_mutex);
 124                }
 125            }
 126        }
 127    }
 128
 129    public bool cancel(RetryTask task)
 130    {
 131        lock (_mutex)
 132        {
 133            // Only remove the task if we cancel it in the timer before it runs. If timer().cancel returns
 134            // false, the task is already executing and runTimerTask will call remove() to erase it; removing
 135            // it here would let destroy() observe an empty queue while the task is still running. When the
 136            // queue is being destroyed (_instance is null) every remaining task is running and is likewise
 137            // removed by remove(), which wakes destroy().
 138            if (_instance != null && _instance.timer().cancel(task))
 139            {
 140                return _requests.Remove(task);
 141            }
 142            return false;
 143        }
 144    }
 145
 146    private Instance _instance;
 147    private Dictionary<RetryTask, object> _requests = new();
 148    private readonly object _mutex = new();
 149}