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