View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.mina.service.idlechecker;
20  
21  import java.util.Collections;
22  import java.util.Set;
23  import java.util.concurrent.ConcurrentHashMap;
24  
25  import org.apache.mina.api.IdleStatus;
26  import org.apache.mina.session.AbstractIoSession;
27  import org.apache.mina.session.AttributeKey;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * An session idle detector using an index in place of polling every session every seconds.<br>
33   * 
34   * For each session read/write event :<br>
35   * <ul>
36   * <li>we calculate what is the supposed future idle date</li>
37   * <li>we round it at the next second</li>
38   * <li>we store a reference to this session in a circular buffer like :</li>
39   * </ul>
40   * 
41   * <pre>
42   * 
43   *               +--- Current time
44   *               |
45   *               v
46   * +---+---+...+---+---+...+---+
47   * | 0 | 1 |   | T |T+1|   |599|
48   * +---+---+...+---+---+...+---+
49   *               |   |
50   *               |   +--> { S2, S7, S12...} (sessions that will TO in one second)
51   *               +------> { S5, S6, S8...} (sessions that are idle for the maximum delay of 1 hour )
52   * </pre>
53   * 
54   * The maximum idle itme is one hour.
55   * 
56   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
57   */
58  public class IndexedIdleChecker implements IdleChecker {
59      /** Maximum idle time in second : default to 1 hour */
60      private static final int MAX_IDLE_TIME_IN_SEC = 60 * 60;
61  
62      /** Maximum idle time in milliseconds : default to 1 hour */
63      private static final long MAX_IDLE_TIME_IN_MS = MAX_IDLE_TIME_IN_SEC * 1000L;
64  
65      /** A logger for this class */
66      private static final Logger LOG = LoggerFactory.getLogger(IndexedIdleChecker.class);
67  
68      // A speedup for logs
69      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
70  
71      private static final AttributeKey<Integer> READ_IDLE_INDEX = AttributeKey.createKey(Integer.class,
72              "idle.read.index");
73  
74      private static final AttributeKey<Integer> WRITE_IDLE_INDEX = AttributeKey.createKey(Integer.class,
75              "idle.write.index");
76  
77      private long lastCheckTimeMs = System.currentTimeMillis();
78  
79      @SuppressWarnings("unchecked")
80      private final Set<AbstractIoSession>[] readIdleSessionIndex = new Set[MAX_IDLE_TIME_IN_SEC];
81  
82      @SuppressWarnings("unchecked")
83      private final Set<AbstractIoSession>[] writeIdleSessionIndex = new Set[MAX_IDLE_TIME_IN_SEC];
84  
85      /** The elapsed period between two checks : 1 second */
86      private static final int GRANULARITY_IN_MS = 1000;
87  
88      private final Worker worker = new Worker();
89  
90      private volatile boolean running = true;
91  
92      /**
93       * {@inheritDoc}
94       */
95      @Override
96      public void start() {
97          worker.start();
98      }
99  
100     /**
101      * {@inheritDoc}
102      */
103     @Override
104     public void destroy() {
105         running = false;
106         try {
107             // interrupt the sleep
108             worker.interrupt();
109             // wait for worker to stop
110             worker.join();
111         } catch (InterruptedException e) {
112             // interrupted, we don't care much
113         }
114     }
115 
116     /**
117      * {@inheritDoc}
118      */
119     @Override
120     public void sessionRead(AbstractIoSession session, long timeInMs) {
121         if (IS_DEBUG) {
122             LOG.debug("session read event, compute idle index of session {}", session);
123         }
124 
125         // remove from the old index position
126         Integer oldIndex = session.getAttribute(READ_IDLE_INDEX);
127 
128         if (oldIndex != null && readIdleSessionIndex[oldIndex] != null) {
129             if (IS_DEBUG) {
130                 LOG.debug("remove for old index {}", oldIndex);
131             }
132 
133             readIdleSessionIndex[oldIndex].remove(session);
134         }
135 
136         long idleTimeInMs = session.getConfig().getIdleTimeInMillis(IdleStatus.READ_IDLE);
137 
138         // is idle enabled ?
139         if (idleTimeInMs <= 0L) {
140             if (IS_DEBUG) {
141                 LOG.debug("no read idle configuration");
142             }
143         } else {
144             int nextIdleTimeInSeconds = (int) ((timeInMs + idleTimeInMs) / 1000L);
145             int index = nextIdleTimeInSeconds % MAX_IDLE_TIME_IN_SEC;
146 
147             if (IS_DEBUG) {
148                 LOG.debug("computed index : {}", index);
149             }
150 
151             if (readIdleSessionIndex[index] == null) {
152                 readIdleSessionIndex[index] = Collections
153                         .newSetFromMap(new ConcurrentHashMap<AbstractIoSession, Boolean>());
154             }
155 
156             if (IS_DEBUG) {
157                 LOG.debug("marking session {} idle for index {}", session, index);
158             }
159 
160             readIdleSessionIndex[index].add(session);
161             session.setAttribute(READ_IDLE_INDEX, index);
162         }
163     }
164 
165     /**
166      * {@inheritDoc}
167      */
168     @Override
169     public void sessionWritten(AbstractIoSession session, long timeInMs) {
170         if (IS_DEBUG) {
171             LOG.debug("session write event, compute idle index of session {}", session);
172         }
173 
174         // remove from the old index position
175         Integer oldIndex = session.getAttribute(WRITE_IDLE_INDEX);
176 
177         if (oldIndex != null && writeIdleSessionIndex[oldIndex] != null) {
178             if (IS_DEBUG) {
179                 LOG.debug("remove for old index {}", oldIndex);
180             }
181 
182             writeIdleSessionIndex[oldIndex].remove(session);
183         }
184 
185         long idleTimeInMs = session.getConfig().getIdleTimeInMillis(IdleStatus.WRITE_IDLE);
186 
187         // is idle enabled ?
188         if (idleTimeInMs <= 0L) {
189             if (IS_DEBUG) {
190                 LOG.debug("no write idle configuration");
191             }
192         } else {
193             int nextIdleTimeInSeconds = (int) ((timeInMs + idleTimeInMs) / 1000L);
194             int index = nextIdleTimeInSeconds % MAX_IDLE_TIME_IN_SEC;
195 
196             if (writeIdleSessionIndex[index] == null) {
197                 writeIdleSessionIndex[index] = Collections
198                         .newSetFromMap(new ConcurrentHashMap<AbstractIoSession, Boolean>());
199             }
200 
201             writeIdleSessionIndex[index].add(session);
202             session.setAttribute(WRITE_IDLE_INDEX, index);
203         }
204     }
205 
206     /**
207      * {@inheritDoc}
208      */
209     @Override
210     public int processIdleSession(long timeMs) {
211         int counter = 0;
212         long delta = timeMs - lastCheckTimeMs;
213 
214         if (LOG.isDebugEnabled()) {
215             LOG.debug("checking idle time, last = {}, now = {}, delta = {}", new Object[] { lastCheckTimeMs, timeMs,
216                     delta });
217         }
218 
219         if (delta < 1000) {
220             LOG.debug("not a second between the last checks, abort");
221             return 0;
222         }
223 
224         // if (lastCheckTimeMs == 0) {
225         // LOG.debug("first check, we start now");
226         // lastCheckTimeMs = System.currentTimeMillis() - 1000;
227         // }
228         int startIdx = ((int) (Math.max(lastCheckTimeMs, timeMs - MAX_IDLE_TIME_IN_MS + 1) / 1000L))
229                 % MAX_IDLE_TIME_IN_SEC;
230         int endIdx = ((int) (timeMs / 1000L)) % MAX_IDLE_TIME_IN_SEC;
231 
232         LOG.debug("scaning from index {} to index {}", startIdx, endIdx);
233 
234         int index = startIdx;
235         do {
236 
237             LOG.trace("scanning index {}", index);
238             // look at the read idle index
239             counter += processIndex(readIdleSessionIndex, index, IdleStatus.READ_IDLE);
240             counter += processIndex(writeIdleSessionIndex, index, IdleStatus.WRITE_IDLE);
241 
242             index = (index + 1) % MAX_IDLE_TIME_IN_SEC;
243         } while (index != endIdx);
244 
245         // save last check time for next call
246         lastCheckTimeMs = timeMs;
247         LOG.debug("detected {} idleing sessions", counter);
248         return counter;
249     }
250 
251     private int processIndex(Set<AbstractIoSession>[] indexByTime, int position, IdleStatus status) {
252         Set<AbstractIoSession> sessions = indexByTime[position];
253 
254         if (sessions == null) {
255             return 0;
256         }
257 
258         int counter = 0;
259 
260         for (AbstractIoSession idleSession : sessions) {
261             idleSession.setAttribute(status == IdleStatus.READ_IDLE ? READ_IDLE_INDEX : WRITE_IDLE_INDEX, null);
262             // check if idle detection wasn't disabled since the index update
263             if (idleSession.getConfig().getIdleTimeInMillis(status) > 0) {
264                 idleSession.processSessionIdle(status);
265             }
266             counter++;
267         }
268         // clear the processed index entry
269         indexByTime[position] = null;
270         return counter;
271     }
272 
273     /**
274      * Thread in charge of checking the idleing sessions and fire events
275      */
276     private class Worker extends Thread {
277 
278         public Worker() {
279             super("IdleChecker");
280             setDaemon(true);
281         }
282 
283         @Override
284         public void run() {
285             while (running) {
286                 try {
287                     sleep(GRANULARITY_IN_MS);
288                     processIdleSession(System.currentTimeMillis());
289                 } catch (InterruptedException e) {
290                     break;
291                 }
292             }
293         }
294     }
295 }