< Summary

Information
Class: Ice.Internal.Timer.Token
Assembly: Ice
File(s): /_/csharp/src/Ice/Internal/Timer.cs
Tag: 125_37167941578
Line coverage
76%
Covered lines: 16
Uncovered lines: 5
Coverable lines: 21
Total lines: 323
Line coverage: 76.1%
Branch coverage
58%
Covered branches: 7
Total branches: 12
Branch coverage: 58.3%
Method coverage
50%
Covered methods: 2
Fully covered methods: 1
Total methods: 4
Method coverage: 50%
Full method coverage: 25%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
CompareTo(...)87.5%8890%
Equals(...)0%2040%
GetHashCode()100%210%

File(s)

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

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Diagnostics;
 4
 5//
 6// NOTE: We don't use C# timers, the API is quite a bit different from
 7// the C++ & Java timers and it's not clear what is the cost of
 8// scheduling and cancelling timers.
 9//
 10
 11namespace Ice.Internal;
 12
 13public interface TimerTask
 14{
 15    void runTimerTask();
 16}
 17
 18public sealed class Timer
 19{
 20    public void destroy()
 21    {
 22        lock (_mutex)
 23        {
 24            if (_instance == null)
 25            {
 26                return;
 27            }
 28
 29            _instance = null;
 30            Monitor.Pulse(_mutex);
 31
 32            _tokens.Clear();
 33            _tasks.Clear();
 34        }
 35
 36        _thread.Join();
 37    }
 38
 39    public void schedule(TimerTask task, TimeSpan delay)
 40    {
 41        if (delay < TimeSpan.Zero || delay > TimeSpan.FromMilliseconds(int.MaxValue))
 42        {
 43            throw new ArgumentOutOfRangeException(
 44                nameof(delay),
 45                $"The timer delay must be between 0 and {int.MaxValue} milliseconds.");
 46        }
 47
 48        lock (_mutex)
 49        {
 50            if (_instance == null)
 51            {
 52                throw new Ice.CommunicatorDestroyedException();
 53            }
 54
 55            var token = new Token(
 56                Time.currentMonotonicTimeMillis() + (long)delay.TotalMilliseconds,
 57                ++_tokenId,
 58                0,
 59                task);
 60
 61            try
 62            {
 63                _tasks.Add(task, token);
 64                _tokens.Add(token, null);
 65            }
 66            catch (ArgumentException)
 67            {
 68                Debug.Assert(false);
 69            }
 70
 71            if (token.scheduledTime < _wakeUpTime)
 72            {
 73                Monitor.Pulse(_mutex);
 74            }
 75        }
 76    }
 77
 78    public bool cancel(TimerTask task)
 79    {
 80        lock (_mutex)
 81        {
 82            if (_instance == null)
 83            {
 84                return false;
 85            }
 86
 87            if (!_tasks.TryGetValue(task, out Token token))
 88            {
 89                return false;
 90            }
 91            _tasks.Remove(task);
 92            _tokens.Remove(token);
 93            return true;
 94        }
 95    }
 96
 97    //
 98    // Only for use by Instance.
 99    //
 100    internal Timer(Instance instance, ThreadPriority priority = ThreadPriority.Normal) =>
 101        init(instance, priority, true);
 102
 103    internal void init(Instance instance, ThreadPriority priority, bool hasPriority)
 104    {
 105        _instance = instance;
 106
 107        string threadName = _instance.initializationData().properties.getIceProperty("Ice.ProgramName");
 108        if (threadName.Length > 0)
 109        {
 110            threadName += "-";
 111        }
 112
 113        _thread = new Thread(new ThreadStart(Run));
 114        _thread.IsBackground = true;
 115        _thread.Name = threadName + "Ice.Timer";
 116        if (hasPriority)
 117        {
 118            _thread.Priority = priority;
 119        }
 120        _thread.Start();
 121    }
 122
 123    internal void updateObserver(Ice.Instrumentation.CommunicatorObserver obsv)
 124    {
 125        lock (_mutex)
 126        {
 127            Debug.Assert(obsv != null);
 128            _observer = obsv.getThreadObserver(
 129                "Communicator",
 130                _thread.Name,
 131                Ice.Instrumentation.ThreadState.ThreadStateIdle,
 132                _observer);
 133            _observer?.attach();
 134        }
 135    }
 136
 137    public void Run()
 138    {
 139        Token token = null;
 140        while (true)
 141        {
 142            lock (_mutex)
 143            {
 144                if (_instance != null)
 145                {
 146                    //
 147                    // If the task we just ran is a repeated task, schedule it
 148                    // again for execution if it wasn't canceled.
 149                    //
 150                    if (token != null && token.delay > 0)
 151                    {
 152                        if (_tasks.ContainsKey(token.task))
 153                        {
 154                            token.scheduledTime = Time.currentMonotonicTimeMillis() + token.delay;
 155                            _tokens.Add(token, null);
 156                        }
 157                    }
 158                }
 159                token = null;
 160
 161                if (_instance == null)
 162                {
 163                    break;
 164                }
 165
 166                if (_tokens.Count == 0)
 167                {
 168                    _wakeUpTime = long.MaxValue;
 169                    Monitor.Wait(_mutex);
 170                }
 171
 172                if (_instance == null)
 173                {
 174                    break;
 175                }
 176
 177                while (_tokens.Count > 0 && _instance != null)
 178                {
 179                    long now = Time.currentMonotonicTimeMillis();
 180
 181                    Token first = null;
 182                    foreach (Token t in _tokens.Keys)
 183                    {
 184                        first = t;
 185                        break;
 186                    }
 187                    Debug.Assert(first != null);
 188
 189                    if (first.scheduledTime <= now)
 190                    {
 191                        _tokens.Remove(first);
 192                        token = first;
 193                        if (token.delay == 0)
 194                        {
 195                            _tasks.Remove(token.task);
 196                        }
 197                        break;
 198                    }
 199
 200                    _wakeUpTime = first.scheduledTime;
 201                    // The wait duration fits in an int: schedule rejects delays larger than int.MaxValue, and the
 202                    // monotonic clock only moves forward after that.
 203                    Debug.Assert(first.scheduledTime - now <= int.MaxValue);
 204                    Monitor.Wait(_mutex, (int)(first.scheduledTime - now));
 205                }
 206
 207                if (_instance == null)
 208                {
 209                    break;
 210                }
 211            }
 212
 213            if (token != null)
 214            {
 215                try
 216                {
 217                    Ice.Instrumentation.ThreadObserver threadObserver = _observer;
 218                    if (threadObserver != null)
 219                    {
 220                        threadObserver.stateChanged(
 221                            Ice.Instrumentation.ThreadState.ThreadStateIdle,
 222                            Ice.Instrumentation.ThreadState.ThreadStateInUseForOther);
 223                        try
 224                        {
 225                            token.task.runTimerTask();
 226                        }
 227                        finally
 228                        {
 229                            threadObserver.stateChanged(
 230                                Ice.Instrumentation.ThreadState.ThreadStateInUseForOther,
 231                                Ice.Instrumentation.ThreadState.ThreadStateIdle);
 232                        }
 233                    }
 234                    else
 235                    {
 236                        token.task.runTimerTask();
 237                    }
 238                }
 239                catch (System.Exception ex)
 240                {
 241                    lock (_mutex)
 242                    {
 243                        if (_instance != null)
 244                        {
 245                            string s = "unexpected exception from task run method in timer thread:\n" + ex;
 246                            _instance.initializationData().logger.error(s);
 247                        }
 248                    }
 249                }
 250            }
 251        }
 252    }
 253
 254    private class Token : System.IComparable
 255    {
 1256        public
 1257        Token(long scheduledTime, int id, long delay, TimerTask task)
 258        {
 1259            this.scheduledTime = scheduledTime;
 1260            this.id = id;
 1261            this.delay = delay;
 1262            this.task = task;
 1263        }
 264
 265        public int CompareTo(object o)
 266        {
 267            //
 268            // Token are sorted by scheduled time and token id.
 269            //
 1270            var r = (Token)o;
 1271            if (scheduledTime < r.scheduledTime)
 272            {
 1273                return -1;
 274            }
 1275            else if (scheduledTime > r.scheduledTime)
 276            {
 1277                return 1;
 278            }
 279
 1280            if (id < r.id)
 281            {
 1282                return -1;
 283            }
 1284            else if (id > r.id)
 285            {
 1286                return 1;
 287            }
 288
 0289            return 0;
 290        }
 291
 292        public override bool Equals(object o)
 293        {
 0294            if (ReferenceEquals(this, o))
 295            {
 0296                return true;
 297            }
 0298            return o is not Token t ? false : CompareTo(t) == 0;
 299        }
 300
 0301        public override int GetHashCode() => HashCode.Combine(id, scheduledTime);
 302
 303        public long scheduledTime;
 304        public int id; // Since we can't compare references, we need to use another id.
 305        public long delay;
 306        public TimerTask task;
 307    }
 308
 309    private readonly object _mutex = new object();
 310    private readonly IDictionary<Token, object> _tokens = new SortedDictionary<Token, object>();
 311    private readonly IDictionary<TimerTask, Token> _tasks = new Dictionary<TimerTask, Token>();
 312    private Instance _instance;
 313    private long _wakeUpTime = long.MaxValue;
 314    private int _tokenId;
 315    private Thread _thread;
 316
 317    //
 318    // We use a volatile to avoid synchronization when reading
 319    // _observer. Reference assignment is atomic in Java so it
 320    // also doesn't need to be synchronized.
 321    //
 322    private volatile Ice.Instrumentation.ThreadObserver _observer;
 323}