// // Copyright (c) ZeroC, Inc. All rights reserved. // namespace IceInternal { using System.Diagnostics; using System.Collections.Generic; public sealed class ACMConfig : System.ICloneable { internal ACMConfig(bool server) { timeout = 60 * 1000; heartbeat = Ice.ACMHeartbeat.HeartbeatOnDispatch; close = server ? Ice.ACMClose.CloseOnInvocation : Ice.ACMClose.CloseOnInvocationAndIdle; } public ACMConfig(Ice.Properties p, Ice.Logger l, string prefix, ACMConfig dflt) { Debug.Assert(prefix != null); string timeoutProperty; if((prefix.Equals("Ice.ACM.Client") || prefix.Equals("Ice.ACM.Server")) && p.getProperty(prefix + ".Timeout").Length == 0) { timeoutProperty = prefix; // Deprecated property. } else { timeoutProperty = prefix + ".Timeout"; } timeout = p.getPropertyAsIntWithDefault(timeoutProperty, dflt.timeout / 1000) * 1000; // To milliseconds if(timeout < 0) { l.warning("invalid value for property `" + timeoutProperty + "', default value will be used instead"); timeout = dflt.timeout; } int hb = p.getPropertyAsIntWithDefault(prefix + ".Heartbeat", (int)dflt.heartbeat); if(hb >= (int)Ice.ACMHeartbeat.HeartbeatOff && hb <= (int)Ice.ACMHeartbeat.HeartbeatAlways) { heartbeat = (Ice.ACMHeartbeat)hb; } else { l.warning("invalid value for property `" + prefix + ".Heartbeat" + "', default value will be used instead"); heartbeat = dflt.heartbeat; } int cl = p.getPropertyAsIntWithDefault(prefix + ".Close", (int)dflt.close); if(cl >= (int)Ice.ACMClose.CloseOff && cl <= (int)Ice.ACMClose.CloseOnIdleForceful) { close = (Ice.ACMClose)cl; } else { l.warning("invalid value for property `" + prefix + ".Close" + "', default value will be used instead"); close = dflt.close; } } public object Clone() { return this.MemberwiseClone(); } public int timeout; public Ice.ACMHeartbeat heartbeat; public Ice.ACMClose close; } public interface ACMMonitor : TimerTask { void add(Ice.ConnectionI con); void remove(Ice.ConnectionI con); void reap(Ice.ConnectionI con); ACMMonitor acm(Ice.Optional timeout, Ice.Optional c, Ice.Optional h); Ice.ACM getACM(); } class FactoryACMMonitor : ACMMonitor { internal class Change { internal Change(Ice.ConnectionI connection, bool remove) { this.connection = connection; this.remove = remove; } public readonly Ice.ConnectionI connection; public readonly bool remove; } internal FactoryACMMonitor(Instance instance, ACMConfig config) { _instance = instance; _config = config; } internal void destroy() { lock(this) { if(_instance == null) { // // Ensure all the connections have been cleared, it's important to wait here // to prevent the timer destruction in IceInternal::Instance::destroy. // while(_connections.Count > 0) { System.Threading.Monitor.Wait(this); } return; } if(_connections.Count > 0) { // // Cancel the scheduled timer task and schedule it again now to clear the // connection set from the timer thread. // _instance.timer().cancel(this); _instance.timer().schedule(this, 0); } _instance = null; _changes.Clear(); // // Wait for the connection set to be cleared by the timer thread. // while(_connections.Count > 0) { System.Threading.Monitor.Wait(this); } } } public void add(Ice.ConnectionI connection) { if(_config.timeout == 0) { return; } lock(this) { if(_connections.Count == 0) { _connections.Add(connection); _instance.timer().scheduleRepeated(this, _config.timeout / 2); } else { _changes.Add(new Change(connection, false)); } } } public void remove(Ice.ConnectionI connection) { if(_config.timeout == 0) { return; } lock(this) { Debug.Assert(_instance != null); _changes.Add(new Change(connection, true)); } } public void reap(Ice.ConnectionI connection) { lock(this) { _reapedConnections.Add(connection); } } public ACMMonitor acm(Ice.Optional timeout, Ice.Optional c, Ice.Optional h) { Debug.Assert(_instance != null); ACMConfig config = (ACMConfig)_config.Clone(); if(timeout.HasValue) { config.timeout = timeout.Value * 1000; // To milliseconds } if(c.HasValue) { config.close = c.Value; } if(h.HasValue) { config.heartbeat = h.Value; } return new ConnectionACMMonitor(this, _instance.timer(), config); } public Ice.ACM getACM() { Ice.ACM acm = new Ice.ACM(); acm.timeout = _config.timeout / 1000; acm.close = _config.close; acm.heartbeat = _config.heartbeat; return acm; } internal List swapReapedConnections() { lock(this) { if(_reapedConnections.Count == 0) { return null; } List connections = _reapedConnections; _reapedConnections = new List(); return connections; } } public void runTimerTask() { lock(this) { if(_instance == null) { _connections.Clear(); System.Threading.Monitor.PulseAll(this); return; } foreach(Change change in _changes) { if(change.remove) { _connections.Remove(change.connection); } else { _connections.Add(change.connection); } } _changes.Clear(); if(_connections.Count == 0) { _instance.timer().cancel(this); return; } } // // Monitor connections outside the thread synchronization, so // that connections can be added or removed during monitoring. // long now = Time.currentMonotonicTimeMillis(); foreach(Ice.ConnectionI connection in _connections) { try { connection.monitor(now, _config); } catch(System.Exception ex) { handleException(ex); } } } internal void handleException(System.Exception ex) { lock(this) { if(_instance == null) { return; } _instance.initializationData().logger.error("exception in connection monitor:\n" + ex); } } private Instance _instance; readonly private ACMConfig _config; private HashSet _connections = new HashSet(); private List _changes = new List(); private List _reapedConnections = new List(); } internal class ConnectionACMMonitor : ACMMonitor { internal ConnectionACMMonitor(FactoryACMMonitor parent, Timer timer, ACMConfig config) { _parent = parent; _timer = timer; _config = config; } public void add(Ice.ConnectionI connection) { lock(this) { Debug.Assert(_connection == null); _connection = connection; if(_config.timeout > 0) { _timer.scheduleRepeated(this, _config.timeout / 2); } } } public void remove(Ice.ConnectionI connection) { lock(this) { Debug.Assert(_connection == connection); _connection = null; if(_config.timeout > 0) { _timer.cancel(this); } } } public void reap(Ice.ConnectionI connection) { _parent.reap(connection); } public ACMMonitor acm(Ice.Optional timeout, Ice.Optional c, Ice.Optional h) { return _parent.acm(timeout, c, h); } public Ice.ACM getACM() { Ice.ACM acm = new Ice.ACM(); acm.timeout = _config.timeout / 1000; acm.close = _config.close; acm.heartbeat = _config.heartbeat; return acm; } public void runTimerTask() { Ice.ConnectionI connection; lock(this) { if(_connection == null) { return; } connection = _connection; } try { connection.monitor(Time.currentMonotonicTimeMillis(), _config); } catch(System.Exception ex) { _parent.handleException(ex); } } readonly private FactoryACMMonitor _parent; readonly private Timer _timer; readonly private ACMConfig _config; private Ice.ConnectionI _connection; } }