001    /**
002     * Copyright (C) 2010-2011, FuseSource Corp.  All rights reserved.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.fusesource.hawtdispatch.transport;
017    
018    import org.fusesource.hawtdispatch.Dispatch;
019    
020    import java.util.concurrent.TimeUnit;
021    
022    /**
023     * <p>A HeartBeatMonitor can be used to watch the read and write
024     * activity of a transport and raise events when the write side
025     * or read side has been idle too long.</p>
026     *
027     * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
028     */
029    public class HeartBeatMonitor {
030    
031        Transport transport;
032        long initialWriteCheckDelay;
033        long initialReadCheckDelay;
034        long writeInterval;
035        long readInterval;
036    
037        Runnable onKeepAlive = Dispatch.NOOP;
038        Runnable onDead = Dispatch.NOOP;
039    
040        short session = 0;
041    
042        boolean readSuspendedInterval;
043        short readSuspendCount;
044    
045        public void suspendRead() {
046            readSuspendCount++;
047            readSuspendedInterval = true;
048        }
049    
050        public void resumeRead() {
051            readSuspendCount--;
052        }
053    
054        private void schedule(final short session, long interval, final Runnable func) {
055            if (this.session == session) {
056                transport.getDispatchQueue().executeAfter(interval, TimeUnit.MILLISECONDS, new Runnable() {
057                    public void run() {
058                        if (HeartBeatMonitor.this.session == session) {
059                            func.run();
060                        }
061                    }
062                });
063            }
064        }
065    
066        private void scheduleCheckWrites(final short session) {
067            final ProtocolCodec codec = transport.getProtocolCodec();
068            Runnable func;
069            if (codec == null) {
070                func = new Runnable() {
071                    public void run() {
072                        scheduleCheckWrites(session);
073                    }
074                };
075            } else {
076                final long lastWriteCounter = codec.getWriteCounter();
077                func = new Runnable() {
078                    public void run() {
079                        if (lastWriteCounter == codec.getWriteCounter()) {
080                            onKeepAlive.run();
081                        }
082                        scheduleCheckWrites(session);
083                    }
084                };
085            }
086            schedule(session, writeInterval, func);
087        }
088    
089        private void scheduleCheckReads(final short session) {
090            final ProtocolCodec codec = transport.getProtocolCodec();
091            Runnable func;
092            if (codec == null) {
093                func = new Runnable() {
094                    public void run() {
095                        scheduleCheckReads(session);
096                    }
097                };
098            } else {
099                final long lastReadCounter = codec.getReadCounter();
100                func = new Runnable() {
101                    public void run() {
102                        if (lastReadCounter == codec.getReadCounter() && !readSuspendedInterval && readSuspendCount == 0) {
103                            onDead.run();
104                        }
105                        readSuspendedInterval = false;
106                        scheduleCheckReads(session);
107                    }
108                };
109            }
110            schedule(session, readInterval, func);
111        }
112    
113        public void start() {
114            session++;
115            readSuspendedInterval = false;
116            if (writeInterval != 0) {
117                if (initialWriteCheckDelay != 0) {
118                    transport.getDispatchQueue().executeAfter(initialWriteCheckDelay, TimeUnit.MILLISECONDS, new Runnable() {
119                        public void run() {
120                            scheduleCheckWrites(session);
121                        }
122                    });
123                } else {
124                    scheduleCheckWrites(session);
125                }
126            }
127            if (readInterval != 0) {
128                if (initialReadCheckDelay != 0) {
129                    transport.getDispatchQueue().executeAfter(initialReadCheckDelay, TimeUnit.MILLISECONDS, new Runnable() {
130                        public void run() {
131                            scheduleCheckReads(session);
132                        }
133                    });
134                } else {
135                    scheduleCheckReads(session);
136                }
137            }
138        }
139    
140        public void stop() {
141            session++;
142        }
143    
144    
145        public long getInitialReadCheckDelay() {
146            return initialReadCheckDelay;
147        }
148    
149        public void setInitialReadCheckDelay(long initialReadCheckDelay) {
150            this.initialReadCheckDelay = initialReadCheckDelay;
151        }
152    
153        public long getInitialWriteCheckDelay() {
154            return initialWriteCheckDelay;
155        }
156    
157        public void setInitialWriteCheckDelay(long initialWriteCheckDelay) {
158            this.initialWriteCheckDelay = initialWriteCheckDelay;
159        }
160    
161        public Runnable getOnDead() {
162            return onDead;
163        }
164    
165        public void setOnDead(Runnable onDead) {
166            this.onDead = onDead;
167        }
168    
169        public Runnable getOnKeepAlive() {
170            return onKeepAlive;
171        }
172    
173        public void setOnKeepAlive(Runnable onKeepAlive) {
174            this.onKeepAlive = onKeepAlive;
175        }
176    
177        public long getWriteInterval() {
178            return writeInterval;
179        }
180    
181        public void setWriteInterval(long writeInterval) {
182            this.writeInterval = writeInterval;
183        }
184    
185        public Transport getTransport() {
186            return transport;
187        }
188    
189        public void setTransport(Transport transport) {
190            this.transport = transport;
191        }
192    
193        public long getReadInterval() {
194            return readInterval;
195        }
196    
197        public void setReadInterval(long readInterval) {
198            this.readInterval = readInterval;
199        }
200    }