All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tentackle.dbms.rmi.RemoteDbSessionImpl Maven / Gradle / Ivy

There is a newer version: 21.16.1.0
Show newest version
/**
 * Tentackle - http://www.tentackle.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


package org.tentackle.dbms.rmi;

import org.tentackle.dbms.ConnectionManager;
import org.tentackle.dbms.Db;
import org.tentackle.dbms.DbUtilities;
import org.tentackle.io.ServerSocketConfigurator;
import org.tentackle.io.ServerSocketConfiguratorHolder;
import org.tentackle.io.SocketConfigurator;
import org.tentackle.io.SocketConfiguratorHolder;
import org.tentackle.log.Logger;
import org.tentackle.log.Logger.Level;
import org.tentackle.log.LoggerFactory;
import org.tentackle.log.MethodStatistics;
import org.tentackle.misc.DiagnosticUtilities;
import org.tentackle.misc.Duration;
import org.tentackle.reflect.ReflectionHelper;
import org.tentackle.session.LoginFailedException;
import org.tentackle.session.SessionInfo;
import org.tentackle.session.SessionPool;

import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;



/**
 * User session within the application server.
 *
 * @author  harald
 */
abstract public class RemoteDbSessionImpl implements RemoteDbSession, Exportable {

  /**
   * logger for this class.
   */
  private static final Logger LOGGER = LoggerFactory.getLogger(RemoteDbSessionImpl.class);



  /**
   * We keep an internal set of all sessions via WeakReferences, so the
   * sessions still will be finalized when the session isn't used anymore.
   * The set of sessions is used to determine stale sessions, i.e. ones
   * with timed out db-connections. These are typically caused by clients
   * not properly closing the rmi-session.
   */
  private static final Set> SESSIONS =
          Collections.newSetFromMap(new ConcurrentHashMap<>());


  /**
   * The session cleanup thread.
   */
  private static RemoteDbSessionCleanupThread cleanupThread;



  /**
   * Starts the optional cleanup thread that will
   * monitor the sessions for db-activity.
   *
   * @param checkInterval is the interval in ms
   */
  public static void startCleanupThread(long checkInterval) {
    cleanupThread = new RemoteDbSessionCleanupThread(checkInterval, SESSIONS, 3);
    cleanupThread.start();
  }


  /**
   * Stops the cleanup thread.
   */
  public static synchronized void stopCleanupThread() {
    if (cleanupThread != null && cleanupThread.isAlive()) {
      cleanupThread.requestTermination();
      cleanupThread = null;
    }
  }


  // each session gets a unique number (only for identification in log files)
  private static final AtomicLong LAST_SESSION_NUMBER = new AtomicLong();




  /**
   * Returns a list of all open sessions.
   *
   * @return the sessions, never null
   */
  public static Collection getOpenSessions() {
    Collection openSessions = new ArrayList<>();
    for (WeakReference ref : SESSIONS) {
      RemoteDbSessionImpl session = ref.get();
      if (session != null && session.isOpen()) {
        openSessions.add(session);
      }
    }
    return openSessions;
  }


  /**
   * Checks if the user is already logged in.
   *
   * @param userInfo the user's info
   * @return the userinfo if already logged in, null if not logged in
   */
  public static SessionInfo isUserLoggedIn(SessionInfo userInfo) {
    for (RemoteDbSessionImpl session: getOpenSessions()) {
      SessionInfo sessionSessionInfo = session.getClientSessionInfo();
      if (userInfo != sessionSessionInfo && sessionSessionInfo.equals(userInfo)) {
        return sessionSessionInfo;
      }
    }
    return null;
  }



  /**
   * relation between a class and their delegates.
   */
  private static class DelegateClasses {

    private final Class clazz;                                   // client side class
    private final Class effectiveClass;                          // the effective class, null if same as clazz
    private final Class delegateClass;              // its delegate interface
    private final Class> delegateImplClass;   // delegate implementation class

    private DelegateClasses(Class effectiveClass, Class clazz,
                            Class delegateClass, Class> delegateImplClass) {
      this.effectiveClass = effectiveClass == clazz ? null : effectiveClass;
      this.clazz = clazz;
      this.delegateClass = delegateClass;
      this.delegateImplClass = delegateImplClass;
    }

    private DelegateClasses(Class effectiveClass, Class clazz, RemoteDelegateLocator.Result delegates) {
      this(clazz,
           effectiveClass == null ? delegates.getEffectiveClass() : effectiveClass,
           delegates.getRemoteDelegate(), delegates.getRemoteDelegateImpl());
    }

    @Override
    public String toString() {
      return clazz.getName() + " -> " + delegateClass.getName() + " / " + delegateImplClass.getName();
    }
  }

  // maps classnames to delegate classes
  private static final ConcurrentHashMap DELEGATE_CLASSES_MAP = new ConcurrentHashMap<>();



  // ------------------- end static section ------------------------


  private final RemoteDbConnectionImpl con;           // the connection object
  private final SessionInfo clientInfo;               // saved client info
  private final SessionInfo serverInfo;               // the server info for creating a new Db connection
  private final long sessionNumber;                   // unique session number

  private String clientHost;                          // the client host string
  private int timeout;                                // timeout in polling intervals of the cleanupthread
  private int port;                                   // port for all delegates
  private RMIClientSocketFactory csf;                 // client socket factory for all delegates
  private RMIServerSocketFactory ssf;                 // server socket factory for all delegates

  private int timeoutCount;                           // consecutive timeouts
  private Db db;                                      // the local Db-connection
  private long closedSince;                           // session closed since, 0 if still open
  private String mdcInfo;                             // the cached MDC info
  private final MethodStatistics methodStats;         // statistics for method invocations
                                                      // (managed by invocation handler, null if no stats)
  private final Collection> exportedDelegates;  // delegates to unexport on close

  /**
   * Creates a session on a given connection.
   *
   * @param con the connection
   * @param clientInfo the SessionInfo from the client
   * @param serverInfo the SessionInfo to establish the connection to the database server
   *
   * @throws RemoteException if the session could not initiated.
   */
  public RemoteDbSessionImpl(RemoteDbConnectionImpl con, SessionInfo clientInfo, SessionInfo serverInfo) throws RemoteException {

    this.con = con;
    this.clientInfo = clientInfo;
    this.serverInfo = serverInfo;

    methodStats = new MethodStatistics();
    sessionNumber = LAST_SESSION_NUMBER.incrementAndGet();
    exportedDelegates = new ArrayList<>();

    try {
      clientHost = UnicastRemoteObject.getClientHost();
      db = openDb();
      synchronized (SESSIONS) {   // prevents race condition between verifySessionInfo and SESSIONS.add
        clientInfo.setSince(System.currentTimeMillis());
        verifySessionInfo(clientInfo);
        db.setSessionInfo(clientInfo);    // switch to client info for logging
        SESSIONS.add(new WeakReference<>(this));
      }
    }
    catch (Exception ex)  {
      if (db != null) {
        cleanup(false);
        closeDb(false);
      }
      if (ex instanceof LoginFailedException) {
        throw (LoginFailedException) ex;
      }
      throw new RemoteException("could not setup remote session", ex);
    }

    // config from connection
    port = con.getPort(db, clientInfo, serverInfo);
    csf = con.getClientSocketFactory(db, clientInfo, serverInfo);
    ssf = con.getServerSocketFactory(db, clientInfo, serverInfo);
    timeout = con.getSessionTimeout(db, clientInfo, serverInfo);
  }


  @Override
  public void exportMe() throws RemoteException {
    port = con.exportRemoteObject(this, port, csf, ssf);
    LOGGER.info("begin {0}", this);
  }

  @Override
  public void unexportMe() throws RemoteException {
    con.unexportRemoteObject(this);
  }


  /**
   * Exports the given delegate.
   * 

* Notice that the delegate must not extend {@link UnicastRemoteObject}! * * @param delegate the delegate * @throws RemoteException if export failed */ public void exportRemoteDelegate(RemoteDelegate delegate) throws RemoteException { con.exportRemoteObject(delegate, port, csf, ssf); exportedDelegates.add(new WeakReference<>(delegate)); } /** * Unexports given remote delegate. *

* Notice: when the session is closed, all still exported objects will be unexported. * This method is only necessary if a delegate must be explicitly unexported. * * @param delegate the delegate * @throws RemoteException if object not exported */ public void unexportRemoteDelegate(RemoteDelegate delegate) throws RemoteException { con.unexportRemoteObject(delegate); for (Iterator> iter = exportedDelegates.iterator(); iter.hasNext(); ) { WeakReference ref = iter.next(); RemoteDelegate dg = ref.get(); if (dg == null) { iter.remove(); } else if (dg == delegate) { iter.remove(); break; } } } /** * Verifies and updates the client's session info.
* Needs to be implemented by the application.
* Checks login credentials and sets the user id. * * @param sessionInfo the session info * @throws LoginFailedException if login is not allowed for whatever reason */ abstract public void verifySessionInfo(SessionInfo sessionInfo) throws LoginFailedException; /** * Gets the unique session number. * * @return the number of this session */ public long getSessionNumber() { return sessionNumber; } /** * Gets the session timeout. * * @return the timeout in timeout intervals */ public int getTimeout() { return timeout; } /** * Sets the session timeout. * * @param timeout the timeout in timeout intervals */ public void setTimeout(int timeout) { this.timeout = timeout; } /** * Gets the session db connection * @return the db connection */ public Db getSession() { return db; } /** * Gets the server connection. * * @return the connection */ public RemoteDbConnectionImpl getConnection() { return con; } /** * Gets the client user info. * * @return the client user info */ @Override public SessionInfo getClientSessionInfo() { return clientInfo; } /** * Gets the server user info. * * @return the server user info */ public SessionInfo getServerSessionInfo() { return serverInfo; } /** * Gets the epochal time when the session was closed. * * @return the time when closed, 0 if still open */ public long getClosedSince() { return closedSince; } /** * Gets the port for all delegates. * * @return the port for all delegates */ public int getPort() { return port; } /** * Sets Gets the port for all delegates. * * @param port the port */ public void setPort(int port) { this.port = port; } /** * Gets the default client socket factory for all delegates. * * @return the default csf for all delegates */ public RMIClientSocketFactory getClientSocketFactory() { return csf; } /** * Sets the client socket factory for all delegates. * * @param csf the socket factory */ public void setClientSocketFactory(RMIClientSocketFactory csf) { this.csf = csf; } /** * Gets the default server socket factory for all delegates. * * @return the default ssf for all delegates */ public RMIServerSocketFactory getServerSocketFactory() { return ssf; } /** * Sets the server socket factory for all delegates. * * @param ssf the socket factory */ public void setServerSocketFactory(RMIServerSocketFactory ssf) { this.ssf = ssf; } /** * Counts the invocation of a delegate method. * * @param method the method invoked * @param servicedClass the serviced class * @param duration execution duration */ public void countMethodInvocation(Method method, Class servicedClass, Duration duration) { methodStats.countMethodInvocation(method, servicedClass, duration); } /** * Opens a new Db.
* The default implementation opens a new Db. * Can be overridden if, for example, pools are used instead. * @return the db connection * @throws LoginFailedException if opening the db failed */ protected Db openDb() throws LoginFailedException { SessionPool dbPool = DbUtilities.getInstance().getDefaultSessionPool(); ConnectionManager conMgr = DbUtilities.getInstance().getDefaultConnectionManager(); if (dbPool != null) { // if pooled try { Db pooledDb = (Db) dbPool.getSession(); LOGGER.info("using {0}", pooledDb); return pooledDb; } catch (Exception ex) { throw new LoginFailedException("open Db failed", ex); } } else { Db newDb = new Db(conMgr, serverInfo); newDb.open(); return newDb; } } /** * Cleanup the session. *

* The method is invoked whenever the session is closed * due to an ordinary logout or client crash. * The default implementation rolls back any pending transaction. * * @param crashed true if client crashed, else regular logout */ protected void cleanup(boolean crashed) { if (crashed) { LOGGER.warning("cleaning up crashed {0}{1}", this, DiagnosticUtilities.getInstance().createStackDump()); } if (db != null) { db.setCrashed(crashed); db.rollbackImmediately(null); // roll back if anything pending } doLogStatistics(Level.INFO, true); } /** * Logs the statistics. * * @param level the logging level * @param clear true if clear statistics after dump */ protected void doLogStatistics(Level level, boolean clear) { methodStats.logStatistics(level, " >RMI-Stats: ", clear); } /** * Closes the database connection (and thus rolls back any pending transaction). * If the db is pooled, it will be returned to the pool instead of being closed. * @param cleanup true if db must be physically closed even if pooled, due to cleanup */ protected void closeDb(boolean cleanup) { if (db != null) { if (db.isPooled()) { db.rollbackImmediately(null); // roll back if anything pending if (cleanup) { db.close(); // closed db will be removed from the pool in putDb LOGGER.warning("pooled {0} closed due to session cleanup", db); } else { // only return the db to the pool, don't close it LOGGER.info("returning {0} to pool", db); } // reset the user info for this db for sure (the application may have changed it to clientInfo) db.setSessionInfo(serverInfo); db.getPool().putSession(db); } else { db.close(); } db = null; // closed -> to GC closedSince = System.currentTimeMillis(); } } /** * Forces a cleanup if all cleanup and closing failed. *

* Simply marks the session closed and moves all references to GC. */ protected void forceCleanup() { closedSince = System.currentTimeMillis(); Db zombieDb = db; if (zombieDb != null) { db = null; // block any further RMI requests (isOpen() == false) try { zombieDb.close(); // should work if (zombieDb.isPooled()) { zombieDb.getPool().putSession(zombieDb); } } catch (Exception ex) { LOGGER.warning("forced cleanup may be incomplete", ex); } } } /** * Gets the string representation of the client host connected to this session. * * @return the client host string */ public String getClientHostString() { return clientHost; } /** * Returns an application-specific option string.
* For diagnostic purposes only. * * @return null if no options */ public String getOptions() { return null; } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(ReflectionHelper.getClassBaseName(getClass())).append('#').append(sessionNumber).append(": csf="); if (csf == null) { buf.append(""); } else { buf.append(ReflectionHelper.getClassBaseName(csf.getClass())); if (csf instanceof SocketConfiguratorHolder) { SocketConfigurator csc = ((SocketConfiguratorHolder) csf).getSocketConfigurator(); if (csc != null && csc.isValid()) { buf.append('[').append(csc).append(']'); } } } buf.append(", ssf="); if (ssf == null) { buf.append(""); } else { buf.append(ReflectionHelper.getClassBaseName(ssf.getClass())); if (ssf instanceof ServerSocketConfiguratorHolder) { ServerSocketConfigurator ssc = ((ServerSocketConfiguratorHolder) ssf).getSocketConfigurator(); if (ssc != null && ssc.isValid()) { buf.append('[').append(ssc).append(']'); } } } buf.append(", port="); if (port == 0) { buf.append(""); } else { buf.append(port); } buf.append(", timeout=").append(timeout) .append(", client=").append(clientInfo) .append(", host=").append(clientHost); return buf.toString(); } /** * Gets a string useful for mapped diagnostic context info. * * @return the MDC info */ public String getMdcInfo() { if (mdcInfo == null) { StringBuilder buf = new StringBuilder(); buf.append(sessionNumber).append(':'); String userName = clientInfo.getUserName(); if (userName != null && !userName.isEmpty()) { buf.append(userName); } String applicationName = clientInfo.getApplicationName(); if (applicationName != null && !applicationName.isEmpty()) { buf.append('@').append(applicationName); } if (clientInfo.getApplicationId() != 0) { buf.append('#').append(clientInfo.getApplicationId()); } if (clientInfo.isCloned()) { buf.append('*'); } mdcInfo = buf.toString(); } return mdcInfo; } /** * Determines whether the session is open. * * @return true if session is open */ public boolean isOpen() { return db != null; } /** * Cleanup in case someone forgot to logoff(). * * @throws java.lang.Throwable */ @Override protected void finalize() throws Throwable { try { if (isOpen()) { LOGGER.warning("closing unreferenced open session: " + this); } close(); // cleanup in case client forgot } catch (Exception ex) { try { LOGGER.severe("closing unreferenced session '" + this + "' failed in finalizer", ex); } catch (Exception ex2) { // don't stop finalization if just the logging failed } } finally { super.finalize(); } } // ----------------- implements RemoteDbSession ------------------------ @Override public void close() throws RemoteException { try { if (db != null) { LOGGER.info("end {0}", this); for (WeakReference ref : exportedDelegates) { RemoteDelegate delegate = ref.get(); if (delegate != null) { con.unexportRemoteObject(delegate); } } exportedDelegates.clear(); unexportMe(); cleanup(false); closeDb(false); } } catch (Exception ex) { throw new RemoteException("closing db failed", ex); } } @Override public void log(Level level, String message) throws RemoteException { try { LOGGER.log(level, message, null); } catch (Exception ex) { throw new RemoteException("log() failed", ex); } } @Override public void logStatistics(Level level, boolean clear) throws RemoteException { try { doLogStatistics(level, clear); } catch (Exception ex) { throw new RemoteException("logStatistics failed", ex); } } @SuppressWarnings("unchecked") @Override public T getRemoteDelegate(String classname) throws RemoteException { try { DelegateClasses delegateClasses = DELEGATE_CLASSES_MAP.get(classname); if (delegateClasses == null) { // try to find remote class. // Use superclass if no direct implementation found ClassNotFoundException nfe = null; // first exception thrown Class clazz = Class.forName(classname); Class servicedClass = null; Class currentClazz = clazz; while (currentClazz != null) { if (servicedClass == null) { Class nextServicedClass = DbUtilities.getInstance().getServicedClass(currentClazz); if (nextServicedClass != null) { servicedClass = nextServicedClass; } } try { delegateClasses = new DelegateClasses(servicedClass, clazz, RemoteDelegateLocator.getInstance().findRemoteDelegate(currentClazz)); LOGGER.info("created remote delegate class mapping {0}", delegateClasses); DELEGATE_CLASSES_MAP.put(classname, delegateClasses); break; } catch (ClassNotFoundException e) { if (currentClazz == Object.class && nfe != null) { // abort with first exception thrown throw nfe; } if (nfe == null) { nfe = e; // remember } // try superclass currentClazz = currentClazz.getSuperclass(); } } if (delegateClasses == null) { throw new RemoteException("no delegate class found for " + clazz, nfe); } } T delegate = (T) createRemoteDelegate(delegateClasses.delegateClass, delegateClasses.delegateImplClass, delegateClasses.clazz, delegateClasses.effectiveClass); exportRemoteDelegate(delegate); LOGGER.fine("Delegate created for session={0}, class={1}, port={2}, csf={3}, ssf={4}", this, delegateClasses.clazz, port, csf == null ? "" : csf.getClass().getName(), ssf == null ? "" : ssf.getClass().getName()); return delegate; } catch (RemoteException remex) { throw remex; } catch (Exception ex) { throw new RemoteException("coudn't create delegate for " + classname, ex); } } @Override public DbRemoteDelegate getDbRemoteDelegate() throws RemoteException { try { DbRemoteDelegate delegate = createRemoteDelegate(DbRemoteDelegate.class, DbRemoteDelegateImpl.class, Db.class, null); exportRemoteDelegate(delegate); return delegate; } catch (Exception ex) { throw new RemoteException("coudn't create delegate for " + db, ex); } } /** * Creates the classname of the remote delegate interface from the * serviced classname.
* The default implementation returns: * packagename + ".rmi." + classbasename + "RemoteDelegate" * * @param className the name of the class to look for a delegate * @return the classname of the remote delegate for given class */ public String createRemoteDelegateClassName(String className) { int ndx = className.lastIndexOf('.'); String pkgName = className.substring(0, ndx); String clsName = className.substring(ndx + 1); return pkgName + ".rmi." + clsName + "RemoteDelegate"; } /** * Creates the classname of the remote delegate implementation from the * serviced classname.
* The default implementation returns * {@link #createRemoteDelegateClassName(java.lang.String) + "Impl"}. * * @param className the name of the class to look for a delegate * @return the classname of the remote delegate for given class */ public String createRemoteDelegateImplClassName(String className) { return createRemoteDelegateClassName(className) + "Impl"; } /** * Creates a remote delegate for the given class. *

* If the optional configuration arguments are given, the delegate must provide * a method with the following signature: *

   *  public void configureDelegate(Object... args) {
   *    // whatever args stands for
   *  }
   * 
* The optional configuration parameters allow passing additional objects * to the delegate without having to declare the setters in the remote interface. * * @param the delegate class * @param the delegate implementation class * @param delegateClass the interface class * @param delegateImplClass the implementing class * @param clazz the serviced class * @param effectiveClass the optional effective serviced class, null if clazz * @param configArgs optional arguments to configure the delegate * @return the created * @throws InstantiationException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException */ @SuppressWarnings("unchecked") public > T createRemoteDelegateInstance( Class delegateClass, Class delegateImplClass, Class clazz, Class effectiveClass, Object... configArgs) throws InstantiationException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { // get the constructor with args (RemoteDbSessionImpl session, Class clazz) Constructor constructor = effectiveClass == null ? delegateImplClass.getConstructor(RemoteDbSessionImpl.class, Class.class) : delegateImplClass.getConstructor(RemoteDbSessionImpl.class, Class.class, Class.class); // create instance of delegate for the session db T delegate = (T) (effectiveClass == null ? constructor.newInstance(this, clazz) : constructor.newInstance(this, effectiveClass, clazz)); ((RemoteDelegateImpl) delegate).initialize(); // optionally configure the created delegate if (configArgs != null && configArgs.length > 0) { Method configureMethod = delegateImplClass.getMethod("configureDelegate", Object[].class); configureMethod.invoke(delegate, new Object[] { configArgs }); } return delegate; } /** * Creates a remote delegate for the given class.
* Same as {@link #createRemoteDelegateInstance} but with dynamic proxy to allow intercepting. * * @param the remote delegate class * @param the delegate implementation class * @param delegateClass the interface class * @param delegateImplClass the implementing class * @param clazz the serviced class * @param effectiveClass the effectively serviced class, null if clazz * @param configArgs optional configuration arguments passed to {@link #createRemoteDelegateInstance} * @return the created * @throws InstantiationException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException */ @SuppressWarnings("unchecked") public > T createRemoteDelegate( Class delegateClass, Class delegateImplClass, Class clazz, Class effectiveClass, Object... configArgs) throws InstantiationException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { return (T) Proxy.newProxyInstance( delegateImplClass.getClassLoader(), new Class[] { delegateClass, Remote.class }, new RemoteDelegateInvocationHandler((RemoteDelegateImpl) createRemoteDelegateInstance(delegateClass, delegateImplClass, clazz, effectiveClass, configArgs))); } /** * Checks for timeout. * Will internally increment a counter until timeout has reached. * * @return true if timed out */ protected boolean hasTimedOut() { Db ldb = db; // local copy in case of race cond avoids NPE if (ldb == null || ldb.isAlive()) { timeoutCount = 0; } else { timeoutCount++; } return timeoutCount > timeout; } /** * Sets this session as being polled for timeout. */ protected void polled() { Db ldb = db; // local copy in case of race cond avoids NPE if (ldb != null) { ldb.setAlive(false); } } }