/FileExtraction/Java_unseen/dlitz_resin/modules/kernel/src/com/caucho/env/shutdown/ShutdownSystem.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.env.shutdown;

import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.logging.*;

import com.caucho.env.service.*;
import com.caucho.env.warning.WarningService;
import com.caucho.lifecycle.*;
import com.caucho.util.*;

/**
 * The ShutdownSystem manages the Resin shutdown and includes a timeout
 * thread. If the timeout takes longer than shutdown-wait-max, the ShutdownSystem
 * will force a JVM exit.
 */
public class ShutdownSystem extends AbstractResinSubSystem
{
  public static final int START_PRIORITY = 1;

  private static final Logger log = 
    Logger.getLogger(ShutdownSystem.class.getName());
  private static final L10N L = new L10N(ShutdownSystem.class);

  private static final AtomicReference<ShutdownSystem> _activeService
    = new AtomicReference<ShutdownSystem>();
  
  private long _shutdownWaitMax = 120000L;
  
  private boolean _isShutdownOnOutOfMemory = true;

  private WeakReference<ResinSystem> _resinSystemRef;
  private WarningService _warningService;
  
  private Lifecycle _lifecycle = new Lifecycle();
  
  private FailSafeHaltThread _failSafeHaltThread;
  private ShutdownThread _shutdownThread;
  
  private boolean _isEmbedded;
  
  private AtomicReference<ExitCode> _exitCode = 
    new AtomicReference<ExitCode>();
  
  private ShutdownSystem(boolean isEmbedded)
  {
    _isEmbedded = isEmbedded;
    
    _resinSystemRef = new WeakReference<ResinSystem>(ResinSystem.getCurrent());
    
    _warningService = ResinSystem.getCurrentService(WarningService.class);
    
    if (_warningService == null) {
      throw new IllegalStateException(L.l("{0} requires an active {1}",
                                           ShutdownSystem.class.getSimpleName(),
                                           WarningService.class.getSimpleName()));
    }
  }
  
  public static ShutdownSystem createAndAddService()
  {
    return createAndAddService(Alarm.isTest());
  }

  public static ShutdownSystem createAndAddService(boolean isEmbedded)
  {
    ResinSystem system = preCreate(ShutdownSystem.class);
      
    ShutdownSystem service = new ShutdownSystem(isEmbedded);
    system.addService(ShutdownSystem.class, service);
    
    return service;
  }

  public static ShutdownSystem getCurrent()
  {
    return ResinSystem.getCurrentService(ShutdownSystem.class);
  }
  
  public long getShutdownWaitMax()
  {
    return _shutdownWaitMax;
  }
  
  public void setShutdownWaitTime(long shutdownTime)
  {
    _shutdownWaitMax = shutdownTime;
  }

  public void setShutdownOnOutOfMemory(boolean isShutdown)
  {
    _isShutdownOnOutOfMemory = isShutdown;
  }

  public boolean isShutdownOnOutOfMemory()
  {
    return _isShutdownOnOutOfMemory;
  }
  
  /**
   * Returns the current lifecycle state.
   */
  public LifecycleState getLifecycleState()
  {
    return _lifecycle.getState();
  }
  
  public ExitCode getExitCode()
  {
    return _exitCode.get();
  }
  
  /**
   * Start the server shutdown
   */
  public static void shutdownOutOfMemory(String msg)
  {
    ShutdownSystem shutdown = _activeService.get();
    
    if (shutdown != null && ! shutdown.isShutdownOnOutOfMemory()) {
      System.err.println(msg);
      return;
    }
    else {
      shutdownActive(ExitCode.MEMORY, msg);
    }
  }
    
  /**
   * Start the server shutdown
   */
  public static void shutdownActive(ExitCode exitCode, String msg)
  {
    ShutdownSystem shutdown = _activeService.get();
    
    if (shutdown != null) {
      shutdown.shutdown(exitCode, msg);
      return;
    }
    
    shutdown = getCurrent();
    
    if (shutdown != null) {
      shutdown.shutdown(exitCode, msg);
      return;
    }
    
    log.warning("ShutdownService is not active");
    System.out.println("ShutdownService is not active");
  }

  /**
   * Start the server shutdown
   */
  public void shutdown(ExitCode exitCode, String msg)
  {
    startFailSafeShutdown(msg);

    ShutdownThread shutdownThread = _shutdownThread;
    
    if (shutdownThread != null) {
      shutdownThread.startShutdown(exitCode);

      if (! _isEmbedded) {
        waitForShutdown();
      
        System.out.println("Shutdown timeout");
        System.exit(exitCode.ordinal());
      }
    }
    else {
      shutdownImpl(exitCode);
    }
  }

  public void startFailSafeShutdown(String msg)
  {
    startFailSafeShutdown(msg, _shutdownWaitMax);
  }

  public void startFailSafeShutdown(String msg, long period)
  {
    // start the fail-safe thread in case the shutdown fails
    FailSafeHaltThread haltThread = _failSafeHaltThread;

    if (haltThread != null) {
      haltThread.startShutdown(period);
    }

    try {
      _warningService.sendWarning(this, msg);
    } catch (Exception e) {
      log.log(Level.WARNING, e.toString(), e);
    }
  }

  /**
   * Closes the server.
   */
  private void shutdownImpl(ExitCode exitCode)
  {
    // start the fail-safe thread in case the shutdown fails
    FailSafeHaltThread haltThread = _failSafeHaltThread;
    
    if (haltThread != null)
      haltThread.startShutdown();

    if (exitCode == null)
      exitCode = ExitCode.FAIL_SAFE_HALT;
    
    _exitCode.compareAndSet(null, exitCode);

    try {
      try {
        ResinSystem resinSystem = _resinSystemRef.get();
        
        if (resinSystem != null)
          resinSystem.destroy();
      } catch (Throwable e) {
        log.log(Level.WARNING, e.toString(), e);
      } finally {
        _resinSystemRef = null;
      }
    } finally {
      _lifecycle.toDestroy();

      System.err.println("\nShutdown Resin reason: " + exitCode + "\n");

      log.warning("Shutdown Resin reason: " + exitCode);

      if (! _isEmbedded) {
        System.exit(exitCode.ordinal());
      }
    }
  }
  
  private ResinSystem getResinSystem()
  {
    WeakReference<ResinSystem> resinSystemRef = _resinSystemRef;
    
    if (resinSystemRef != null)
      return resinSystemRef.get();
    else
      return null;
  }

  /**
   * Dump threads for debugging
   */
  public void dumpThreads()
  {
  }
  
  //
  // Service API
  //
  
  @Override
  public int getStartPriority()
  {
    return START_PRIORITY;
  }

  /**
   * Starts the server.
   */
  @Override
  public void start()
  {
    _lifecycle.toActive();
    
    _exitCode.set(null);
    
    if (! _isEmbedded) {
      // _activeService.compareAndSet(null, this);
      _activeService.set(this);
    }
    
    if (! Alarm.isTest() && ! _isEmbedded) {
      _failSafeHaltThread = new FailSafeHaltThread();
      _failSafeHaltThread.start();
    }

    if (! _isEmbedded) {
      _shutdownThread = new ShutdownThread();
      _shutdownThread.setDaemon(true);
      _shutdownThread.start();
    }
  }
  
  /**
   * Starts the server.
   */
  @Override
  public void stop()
  {
    _lifecycle.toDestroy();
    
    _activeService.set(null);
    
    FailSafeHaltThread failSafeThread = _failSafeHaltThread;
    
    if (failSafeThread != null)
      failSafeThread.wake();
    
    ShutdownThread shutdownThread = _shutdownThread;
    
    if (shutdownThread != null)
      shutdownThread.wake();
  }

  @Override
  public void destroy()
  {
    _lifecycle.toDestroy();
  }
  
  private void waitForShutdown()
  {
    waitForShutdown(-1);
  }
  
  private void waitForShutdown(long period)
  {
    if (period <= 0)
      period = _shutdownWaitMax;
    
    long expire = System.currentTimeMillis() + period;
    long now;

    while ((now = System.currentTimeMillis()) < expire) {
      try {
        Thread.interrupted();
        Thread.sleep(expire - now);
      } catch (Exception e) {
      }
    }
  }

  @Override
  public String toString()
  {
    ResinSystem resinSystem = getResinSystem();
    
    if (resinSystem != null)
      return getClass().getSimpleName() + "[id=" + resinSystem.getId() + "]";
    else
      return getClass().getSimpleName() + "[closed]";
  }

  class ShutdownThread extends Thread {
    private AtomicReference<ExitCode> _shutdownExitCode
      = new AtomicReference<ExitCode>();

    ShutdownThread()
    {
      setName("resin-shutdown");
      setDaemon(true);
    }

    /**
     * Starts the destroy sequence
     */
    void startShutdown(ExitCode exitCode)
    {
      _shutdownExitCode.compareAndSet(null, exitCode);
      
      wake();
    }
    
    void wake()
    {
      LockSupport.unpark(this);
    }

    @Override
    public void run()
    {
      while (_shutdownExitCode.get() == null 
             && _lifecycle.isActive()
             && _activeService.get() == ShutdownSystem.this) {
        try {
          Thread.interrupted();
          LockSupport.park();
        } catch (Exception e) {
        }
      }

      ExitCode exitCode = _shutdownExitCode.get();
      
      if (exitCode != null) {
        shutdownImpl(exitCode);
      }
    }
  }

  class FailSafeHaltThread extends Thread {
    private volatile boolean _isShutdown;
    private volatile long _period = -1;

    FailSafeHaltThread()
    {
      setName("resin-fail-safe-halt");
      setDaemon(true);
    }

    /**
     * Starts the shutdown sequence
     */
    void startShutdown()
    {
      startShutdown(-1);
    }
    
    /**
     * Starts the shutdown sequence
     */
    void startShutdown(long period)
    {
      _isShutdown = true;
      _period = period;

      wake();
    }
    
    void wake()
    {
      LockSupport.unpark(this);
    }

    @Override
    public void run()
    {
      while (! _isShutdown && _lifecycle.isActive()) {
        try {
          Thread.interrupted();
          LockSupport.park();
        } catch (Exception e) {
        }
      }
      
      if (! _lifecycle.isActive())
        return;
      
      waitForShutdown(_period);

      if (_lifecycle.isActive()) {
        Runtime.getRuntime().halt(ExitCode.FAIL_SAFE_HALT.ordinal());
      }
    }
  }
}
