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 }