/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ignite.internal.processors.timeout; import java.io.Closeable; import java.util.Comparator; import java.util.Iterator; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.processors.GridProcessorAdapter; import org.apache.ignite.internal.util.GridConcurrentSkipListSet; import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.util.worker.GridWorker; import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.thread.IgniteThread; /** * Detects timeout events and processes them. */ public class GridTimeoutProcessor extends GridProcessorAdapter { /** */ private final IgniteThread timeoutWorker; /** Time-based sorted set for timeout objects. */ private final GridConcurrentSkipListSet<GridTimeoutObject> timeoutObjs = new GridConcurrentSkipListSet<>(new Comparator<GridTimeoutObject>() { /** {@inheritDoc} */ @Override public int compare(GridTimeoutObject o1, GridTimeoutObject o2) { int res = Long.compare(o1.endTime(), o2.endTime()); if (res != 0) return res; return o1.timeoutId().compareTo(o2.timeoutId()); } }); /** */ private final Object mux = new Object(); /** * @param ctx Kernal context. */ public GridTimeoutProcessor(GridKernalContext ctx) { super(ctx); timeoutWorker = new IgniteThread(ctx.config().getIgniteInstanceName(), "grid-timeout-worker", new TimeoutWorker()); } /** {@inheritDoc} */ @Override public void start(boolean activeOnStart) { timeoutWorker.start(); if (log.isDebugEnabled()) log.debug("Timeout processor started."); } /** {@inheritDoc} */ @Override public void stop(boolean cancel) throws IgniteCheckedException { U.interrupt(timeoutWorker); U.join(timeoutWorker); if (log.isDebugEnabled()) log.debug("Timeout processor stopped."); } /** * @param timeoutObj Timeout object. */ @SuppressWarnings({"NakedNotify", "CallToNotifyInsteadOfNotifyAll"}) public void addTimeoutObject(GridTimeoutObject timeoutObj) { if (timeoutObj.endTime() <= 0 || timeoutObj.endTime() == Long.MAX_VALUE) // Timeout will never happen. return; boolean added = timeoutObjs.add(timeoutObj); assert added : "Duplicate timeout object found: " + timeoutObj; if (timeoutObjs.firstx() == timeoutObj) { synchronized (mux) { mux.notify(); // No need to notifyAll since we only have one thread. } } } /** * Schedule the specified timer task for execution at the specified * time with the specified period, in milliseconds. * * @param task Task to execute. * @param delay Delay to first execution in milliseconds. * @param period Period for execution in milliseconds or -1. * @return Cancelable to cancel task. */ public CancelableTask schedule(Runnable task, long delay, long period) { assert delay >= 0 : delay; assert period > 0 || period == -1 : period; CancelableTask obj = new CancelableTask(task, U.currentTimeMillis() + delay, period); addTimeoutObject(obj); return obj; } /** * @param timeoutObj Timeout object. */ public void removeTimeoutObject(GridTimeoutObject timeoutObj) { timeoutObjs.remove(timeoutObj); } /** * Handles job timeouts. */ private class TimeoutWorker extends GridWorker { /** * */ TimeoutWorker() { super(ctx.config().getIgniteInstanceName(), "grid-timeout-worker", GridTimeoutProcessor.this.log); } /** {@inheritDoc} */ @Override protected void body() throws InterruptedException { while (!isCancelled()) { long now = U.currentTimeMillis(); for (Iterator<GridTimeoutObject> iter = timeoutObjs.iterator(); iter.hasNext();) { GridTimeoutObject timeoutObj = iter.next(); if (timeoutObj.endTime() <= now) { iter.remove(); if (log.isDebugEnabled()) log.debug("Timeout has occurred: " + timeoutObj); try { timeoutObj.onTimeout(); } catch (Throwable e) { U.error(log, "Error when executing timeout callback: " + timeoutObj, e); if (e instanceof Error) throw e; } } else break; } synchronized (mux) { while (true) { // Access of the first element must be inside of // synchronization block, so we don't miss out // on thread notification events sent from // 'addTimeoutObject(..)' method. GridTimeoutObject first = timeoutObjs.firstx(); if (first != null) { long waitTime = first.endTime() - U.currentTimeMillis(); if (waitTime > 0) mux.wait(waitTime); else break; } else mux.wait(5000); } } } } } /** {@inheritDoc} */ @Override public void printMemoryStats() { X.println(">>>"); X.println(">>> Timeout processor memory stats [igniteInstanceName=" + ctx.igniteInstanceName() + ']'); X.println(">>> timeoutObjsSize: " + timeoutObjs.size()); } /** * */ public class CancelableTask implements GridTimeoutObject, Closeable { /** */ private final IgniteUuid id = IgniteUuid.randomUuid(); /** */ private long endTime; /** */ private final long period; /** */ private volatile boolean cancel; /** */ @GridToStringInclude private final Runnable task; /** * @param task Task to execute. * @param firstTime First time. * @param period Period. */ CancelableTask(Runnable task, long firstTime, long period) { this.task = task; endTime = firstTime; this.period = period; } /** {@inheritDoc} */ @Override public IgniteUuid timeoutId() { return id; } /** {@inheritDoc} */ @Override public long endTime() { return endTime; } /** {@inheritDoc} */ @Override public synchronized void onTimeout() { if (cancel) return; try { task.run(); } finally { if (!cancel && period > 0) { endTime = U.currentTimeMillis() + period; addTimeoutObject(this); } } } /** {@inheritDoc} */ @Override public void close() { cancel = true; synchronized (this) { // Just waiting for task execution end to make sure that task will not be executed anymore. removeTimeoutObject(this); } } /** {@inheritDoc} */ @Override public String toString() { return S.toString(CancelableTask.class, this); } } }