/* * 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 com.sun.jini.outrigger; import java.io.IOException; import java.rmi.RemoteException; import java.util.logging.Level; import java.util.logging.Logger; import net.jini.core.event.UnknownEventException; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.security.ProxyPreparer; import net.jini.space.JavaSpace; import com.sun.jini.constants.ThrowableConstants; import com.sun.jini.config.Config; import com.sun.jini.logging.Levels; import com.sun.jini.thread.TaskManager; import com.sun.jini.thread.RetryTask; import com.sun.jini.thread.WakeupManager; /** * The notifier thread. This thread is responsible for notifying * objects for which interest has been registered. It operates in * transient space as much as possible. Pending notifications will be * lost when the server goes down, but registrations of interest * survive across server crashes for persistent servers. * * @author Sun Microsystems, Inc. * * @see JavaSpace#notify * @see OutriggerServerImpl#notify * @see NotifyChit */ class Notifier implements com.sun.jini.constants.TimeConstants { /** * The object to use for the <code>source</code> when creating * events. */ private final JavaSpace source; /** Proxy preparer to use on recovered listeners */ private final ProxyPreparer recoveredListenerPreparer; /** wakeup manager for <code>NotifyTask</code> */ private final WakeupManager wakeupMgr = new WakeupManager(new WakeupManager.ThreadDesc(null, true)); /** pending notifications tasks */ private final TaskManager pending; private final static int MAX_ATTEMPTS = 10; // max times to retry /** Logger for logging event related information */ private static final Logger logger = Logger.getLogger(OutriggerServerImpl.eventLoggerName); /** * Create a notifier connected to the given <code>space</code>. * @param source the value to use for the <code>source</code> in * remote event objects. * @param recoveredListenerPreparer <code>ProxyPreparer</code> to * apply to recovered listeners. * @param config a source of configuration data.a * @throws ConfigurationException if there is a problem * with the passed configuration. * @throws NullPointerException if <code>source</code> or * <code>config</code> arguments are <code>null</code>. */ Notifier(JavaSpace source, ProxyPreparer recoveredListenerPreparer, Configuration config) throws ConfigurationException { if (source == null) throw new NullPointerException("source must be non-null"); this.source = source; this.recoveredListenerPreparer = recoveredListenerPreparer; pending = (TaskManager)Config.getNonNullEntry(config, OutriggerServerImpl.COMPONENT_NAME, "notificationsTaskManager", TaskManager.class, new TaskManager()); } /** * Terminate the notifier, shutting down any threads * it has running. This method can assume that * the constructor completed. */ void terminate() { pending.terminate(); wakeupMgr.stop(); wakeupMgr.cancelAll(); } /** * Queue up an event for delivery. * @param sender An object that on request will * attempt to deliver its event * to the associated listener. * @throws NullPointerException if <code>sender</code> is * <code>null</code> */ void enqueueDelivery(EventSender sender) { pending.add(new NotifyTask(sender)); } /* * Static stuff for Pending (can't put it in the class, unfortunately). */ // 1 day =hrs mins secs milliseconds private static final long MAX_TIME = 1 * DAYS; private static final long delays[] = { 1 * SECONDS, 5 * SECONDS, 10 * SECONDS, 60 * SECONDS, 60 * SECONDS }; static { /* * Make the delays the amount of time since the start -- it * is easier to declare the intervals, but the elapsed time is * more <i>useful</i>. */ for (int i = 1; i < delays.length; i++) delays[i] += delays[i - 1]; } /** * A task that represent a notification of matching a particular * template under a given transaction. */ private class NotifyTask extends RetryTask { /** Who and what to send a event to. */ private final EventSender sender; /** * Create an object to represent this list of chits needing * notification. * @param sender An object that on request will * attempt to deliver its event * to the associated listener. * @throws NullPointerException if <code>sender</code> is * <code>null</code> */ NotifyTask(EventSender sender) { super(Notifier.this.pending, Notifier.this.wakeupMgr); if (sender == null) throw new NullPointerException("sender must be non-null"); this.sender = sender; } /** * Try to notify the target. Return <code>true</code> if the * notification was successful. * <p> * We know that we are the only one dealing with the given chit * because <code>runAfter</code> makes sure of it. */ public boolean tryOnce() { long curTime = System.currentTimeMillis(); if (curTime - startTime() > MAX_TIME) { if (logger.isLoggable(Levels.FAILED)) { logger.log(Levels.FAILED, "giving up on delivering event, keeping registration"); } return true; // just stop here, we are declaring "success" } boolean successful = true; // notification successful? try { sender.sendEvent(source, curTime, recoveredListenerPreparer); } catch (UnknownEventException e) { // they didn't want to know about this, so stop them getting // future notifications, too. logFailure("UnknownEventException", Level.FINER, true, e); sender.cancelRegistration(); // this is still "successful" -- we know to stop sending this } catch (RemoteException e) { final int cat = ThrowableConstants.retryable(e); if (cat == ThrowableConstants.BAD_INVOCATION || cat == ThrowableConstants.BAD_OBJECT) { // Listener probably bad, retry likely to fail. logFailure("definite exception", Level.INFO, true, e); sender.cancelRegistration(); } else if (cat == ThrowableConstants.INDEFINITE) { // try, try, again logFailure("indefinite exception", Levels.FAILED, false, e); successful = false; } else if (cat == ThrowableConstants.UNCATEGORIZED) { // Same as above but log differently. logFailure("uncategorized exception", Level.INFO, false, e); successful = false; } else { logger.log(Level.WARNING, "ThrowableConstants.retryable " + "returned out of range value, " + cat, new AssertionError(e)); successful = false; } } catch (IOException e) { // corrupted listener? unlikely to get better, cancel logFailure("IOException", Level.INFO, true, e); sender.cancelRegistration(); } catch (ClassNotFoundException e) { // probably a codebase problem, retry logFailure("ClassNotFoundException", Levels.FAILED, false, e); successful = false; } catch (RuntimeException e) { /* bad listener, or preparer, either way unlikely to * get better */ logFailure("RuntimeException", Level.INFO, true, e); sender.cancelRegistration(); } if (!successful && attempt() > MAX_ATTEMPTS) { if (logger.isLoggable(Levels.FAILED)) { logger.log(Levels.FAILED, "giving up on delivering event, keeping registration"); } return true; // as successful as we're going to be } return successful; } public boolean runAfter(java.util.List list, int max) { for (int i = 0; i < max; i++) { Object task = list.get(i); if (task instanceof NotifyTask) { NotifyTask nt = (NotifyTask)task; if (sender.runAfter(nt.sender)) return true; } } return false; } /** Log a failed delivery attempt */ private void logFailure(String exceptionDescription, Level level, boolean terminal, Throwable t) { if (logger.isLoggable(level)) { logger.log(level, "Encountered " + exceptionDescription + "while preparing to send/sending event, " + (terminal?"dropping":"keeping") + " registration", t); } } } }