| | 1 | | // Copyright (c) ZeroC, Inc. |
| | 2 | |
|
| | 3 | | #nullable enable |
| | 4 | |
|
| | 5 | | using System.Diagnostics; |
| | 6 | | using System.Net.Sockets; |
| | 7 | |
|
| | 8 | | namespace Ice.Internal; |
| | 9 | |
|
| | 10 | | #pragma warning disable CA1001 // _readTimer and _writeTimer are disposed by destroy. |
| | 11 | | internal sealed class IdleTimeoutTransceiverDecorator : Transceiver |
| | 12 | | #pragma warning restore CA1001 |
| | 13 | | { |
| | 14 | | private readonly Transceiver _decoratee; |
| | 15 | | private readonly TimeSpan _idleTimeout; |
| | 16 | | private readonly System.Threading.Timer? _readTimer; |
| | 17 | | private readonly System.Threading.Timer _writeTimer; |
| | 18 | |
|
| | 19 | | // Called by ConnectionI with its mutex locked. |
| 1 | 20 | | internal bool idleCheckEnabled { get; private set; } |
| | 21 | |
|
| 1 | 22 | | public override string ToString() => _decoratee.ToString()!; |
| | 23 | |
|
| 0 | 24 | | public EndpointI? bind() => _decoratee.bind(); |
| | 25 | |
|
| 1 | 26 | | public void checkSendSize(Buffer buf) => _decoratee.checkSendSize(buf); |
| | 27 | |
|
| | 28 | | public void close() |
| | 29 | | { |
| 1 | 30 | | disableIdleCheck(); |
| 1 | 31 | | cancelWriteTimer(); |
| 1 | 32 | | _decoratee.close(); |
| 1 | 33 | | } |
| | 34 | |
|
| 1 | 35 | | public int closing(bool initiator, LocalException ex) => _decoratee.closing(initiator, ex); |
| | 36 | |
|
| | 37 | | public void destroy() |
| | 38 | | { |
| 1 | 39 | | disableIdleCheck(); |
| 1 | 40 | | cancelWriteTimer(); |
| 1 | 41 | | _decoratee.destroy(); |
| 1 | 42 | | _readTimer?.Dispose(); |
| 1 | 43 | | _writeTimer.Dispose(); |
| 1 | 44 | | } |
| | 45 | |
|
| 0 | 46 | | public Socket fd() => _decoratee.fd(); |
| | 47 | |
|
| | 48 | | public void finishRead(Buffer buf) |
| | 49 | | { |
| 1 | 50 | | if (idleCheckEnabled) |
| | 51 | | { |
| | 52 | | // We don't want the idle check to run while we're reading, so we reschedule it before reading. |
| 1 | 53 | | rescheduleReadTimer(); |
| | 54 | | } |
| 1 | 55 | | _decoratee.finishRead(buf); |
| 1 | 56 | | } |
| | 57 | |
|
| | 58 | | // We call write after finishWrite, so no need to do anything here. |
| 1 | 59 | | public void finishWrite(Buffer buf) => _decoratee.finishWrite(buf); |
| | 60 | |
|
| | 61 | | public ConnectionInfo getInfo(bool incoming, string adapterName, string connectionId) => |
| 1 | 62 | | _decoratee.getInfo(incoming, adapterName, connectionId); |
| | 63 | |
|
| | 64 | | public int initialize(Buffer readBuffer, Buffer writeBuffer, ref bool hasMoreData) => |
| 1 | 65 | | _decoratee.initialize(readBuffer, writeBuffer, ref hasMoreData); |
| | 66 | |
|
| 0 | 67 | | public string protocol() => _decoratee.protocol(); |
| | 68 | |
|
| | 69 | | public int read(Buffer buf, ref bool hasMoreData) |
| | 70 | | { |
| | 71 | | // We don't want the idle check to run while we're reading, so we reschedule it before reading. |
| 1 | 72 | | if (idleCheckEnabled) |
| | 73 | | { |
| 1 | 74 | | rescheduleReadTimer(); |
| | 75 | | } |
| 1 | 76 | | return _decoratee.read(buf, ref hasMoreData); |
| | 77 | | } |
| | 78 | |
|
| 1 | 79 | | public void setBufferSize(int rcvSize, int sndSize) => _decoratee.setBufferSize(rcvSize, sndSize); |
| | 80 | |
|
| | 81 | | // We always call finishRead or read to actually read the data. |
| | 82 | | public bool startRead(Buffer buf, AsyncCallback callback, object state) => |
| 1 | 83 | | _decoratee.startRead(buf, callback, state); |
| | 84 | |
|
| | 85 | | // startWrite is called right after write returns SocketOperationWrite. The logic in write is sufficient. |
| | 86 | | public bool startWrite(Buffer buf, AsyncCallback callback, object state, out bool messageWritten) => |
| 1 | 87 | | _decoratee.startWrite(buf, callback, state, out messageWritten); |
| | 88 | |
|
| 0 | 89 | | public string toDetailedString() => _decoratee.toDetailedString(); |
| | 90 | |
|
| | 91 | | public int write(Buffer buf) |
| | 92 | | { |
| 1 | 93 | | cancelWriteTimer(); |
| 1 | 94 | | int op = _decoratee.write(buf); |
| 1 | 95 | | if (op == SocketOperation.None) // write completed |
| | 96 | | { |
| 1 | 97 | | rescheduleWriteTimer(); |
| | 98 | | } |
| 1 | 99 | | return op; |
| | 100 | | } |
| | 101 | |
|
| 1 | 102 | | internal IdleTimeoutTransceiverDecorator( |
| 1 | 103 | | Transceiver decoratee, |
| 1 | 104 | | ConnectionI connection, |
| 1 | 105 | | TimeSpan idleTimeout, |
| 1 | 106 | | bool enableIdleCheck) |
| | 107 | | { |
| | 108 | | Debug.Assert(idleTimeout > TimeSpan.Zero); |
| | 109 | |
|
| 1 | 110 | | _decoratee = decoratee; |
| 1 | 111 | | _idleTimeout = idleTimeout; |
| | 112 | |
|
| 1 | 113 | | if (enableIdleCheck) |
| | 114 | | { |
| 1 | 115 | | _readTimer = new System.Threading.Timer(_ => connection.idleCheck(_idleTimeout)); |
| | 116 | |
|
| | 117 | | // idleCheckEnabled remains false for now. ConnectionI calls enableIdleCheck() when it becomes Active to |
| | 118 | | // schedule the first idle check and set idleCheckEnabled to true. |
| | 119 | | // We don't want the idle check to start when a client connection is waiting for the initial |
| | 120 | | // ValidateConnection message or when a server connection is in its initial StateHolding state. |
| | 121 | | } |
| 1 | 122 | | _writeTimer = new System.Threading.Timer(_ => connection.sendHeartbeat()); |
| 1 | 123 | | } |
| | 124 | |
|
| | 125 | | internal void enableIdleCheck() |
| | 126 | | { |
| 1 | 127 | | if (!idleCheckEnabled && _readTimer is not null) |
| | 128 | | { |
| 1 | 129 | | rescheduleReadTimer(); |
| 1 | 130 | | idleCheckEnabled = true; |
| | 131 | | } |
| 1 | 132 | | } |
| | 133 | |
|
| | 134 | | internal void disableIdleCheck() |
| | 135 | | { |
| 1 | 136 | | if (idleCheckEnabled && _readTimer is not null) |
| | 137 | | { |
| 1 | 138 | | cancelReadTimer(); |
| 1 | 139 | | idleCheckEnabled = false; |
| | 140 | | } |
| 1 | 141 | | } |
| | 142 | |
|
| 1 | 143 | | internal void scheduleHeartbeat() => rescheduleWriteTimer(); |
| | 144 | |
|
| 1 | 145 | | private void cancelReadTimer() => _readTimer!.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); |
| | 146 | |
|
| 1 | 147 | | private void cancelWriteTimer() => _writeTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); |
| | 148 | |
|
| 1 | 149 | | private void rescheduleReadTimer() => _readTimer?.Change(_idleTimeout, Timeout.InfiniteTimeSpan); |
| | 150 | |
|
| 1 | 151 | | private void rescheduleWriteTimer() => _writeTimer.Change(_idleTimeout / 2, Timeout.InfiniteTimeSpan); |
| | 152 | | } |