/*************************************************************************** * Copyright (c) 2012-2013 VMware, Inc. All Rights Reserved. * Licensed 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.vmware.aurora.vc.vcservice; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import org.apache.log4j.Logger; import com.vmware.aurora.exception.VcException; import com.vmware.aurora.util.AuAssert; import com.vmware.vim.binding.vim.fault.NotAuthenticated; import com.vmware.vim.vmomi.core.exception.InternalException; import com.vmware.vim.vmomi.core.exception.UnmarshallException; /** * A long running tasks in VC generally return a task object to monitor * progress and return immediately. Synchronous task executions are * meant to be short, but some may not be. * * This handler is for VC long running tasks that do not return a task object. * If the task runs longer than HTTP session timeout, an HTTP timeout exception * would be thrown. As a result, such exception shouldn't be treated as * VC connection failure. * * Besides, as the call would block the HTTP request thread, we shouldn't * let such calls share the same thread/thread pool as other requests. * Thus, this thread is specially designed to handle this case. */ public class VcLongCallHandler extends Thread { private static Logger logger = Logger.getLogger(VcLongCallHandler.class); static final int LONGCALL_RETRIES = 3; /** * A long running VC call. * * @param <T> type of result (Void if no result) */ public static abstract class VcLongCall<T> { private int retries = LONGCALL_RETRIES; private boolean done = false; private T result = null; private Throwable exception = null; public VcLongCall() { } /** * Implementation of the VC call executed by the VcLongCallHandler thread. * @return * @throws Exception */ abstract protected T callVc() throws Exception; /** * Undo the VC call. Override if requires actual VC call. * @throws Exception */ protected void callUndoVc() throws Exception { } /* * Block and wait for VC call to finish. */ private synchronized T waitForResult() { while (!done) { try { wait(); } catch (InterruptedException e) { logger.info("ignore interruption waiting for long call"); } } if (exception != null) { throw VcException.GENERAL_ERROR(exception); } return result; } /* * Execute VC call and notify the caller thread. */ private synchronized void exec() { try { if (--retries > 0) { if (exception != null) { callUndoVc(); } // execute the VC call result = callVc(); // clear the exception if the call succeeds exception = null; } } catch (Throwable e) { // remember the last exception exception = e; if (e instanceof RejectedExecutionException || e instanceof IllegalStateException || e instanceof NotAuthenticated || e instanceof InternalException || e instanceof UnmarshallException) { /* * Throw exception on possible VC connection errors. * The outer loop will retry the current call. */ throw (RuntimeException)e; } else { logger.info("skipping regular long call exception " + e); } } /* We are done with this call. Here are the cases: * 1. The call is executed successfully. (exception == null) * 2. The call fails with non VC connection exception. * 3. The call fails with a VC connection exception after a few attempts. */ done = true; notifyAll(); } } private Semaphore initSema; // Signals when init completes. private LinkedBlockingQueue<VcLongCall<?>> calls; // FIFO queue of calls /* Written by other threads */ private volatile boolean stopRequested; // Stop requested? protected VcLongCallHandler() { stopRequested = false; initSema = new Semaphore(0); calls = new LinkedBlockingQueue<VcLongCall<?>>(); setName("VcLongCallHandler"); } /** * VcLongCallHandler creator thread can wait here until the listener is * fully initialized. * @throws InterruptedException */ public void waitUntilStarted() throws InterruptedException { AuAssert.check(Thread.currentThread() != this); initSema.acquire(); } /** * Request Call Handler shutdown and wait until it completes. * @throws InterruptedException */ public void shutDown() { if (Thread.currentThread() == this) { return; } /* Wait until the Long Call Hander breaks out of the loops. */ stopRequested = true; interrupt(); while (true) { try { join(); logger.info("VcLongCallHandler joined"); break; } catch (InterruptedException e) { logger.debug("Interrupted while waiting for VcLongCallHandler shutdown"); } } } /* * Loop for getting calls and process them. */ private void processCallsLoop() { VcLongCall<?> call = null; while (!stopRequested) { try { // Only take the next call if the previous call is done. if (call == null || call.done) { call = calls.take(); } call.exec(); } catch (InterruptedException e) { logger.debug("long call handler interrupted"); } catch (Throwable e) { logger.error("VcLongCallHandler got runtime exception", e); /* * VC session went bad. Logout to force automatic relogin that will * trigger the establishment of the new VC session. On stop, logout * happens along with other cleanup. */ VcService vcService = VcContext.getService(); if (vcService == null) { /* * Tomcat likes to clean out ThreadLocal on shutdown. We might land here * if something went wrong with the thread executing WebServiceContextListener * callbacks before it had a chance to cleanly shut down VcEventListener. * Terminate the thread - Bug 733665. */ logger.info("thread local variable for vcService already cleared"); break; } else { logger.info("logout vcService " + vcService.getServiceName()); try { vcService.logout(); } catch (Throwable t) { logger.error("VcLongCallHandler met unexpected exception", t); } } } } } /** * Activate VC Long Call Handler thread. */ public void run() { VcContext.startLongCallSession(); try { initSema.release(); // Wake up the creator. processCallsLoop(); } finally { VcContext.endSession(); } } /** * Run a VC long call and wait for result. * @param <T> type of the result * @param call * @return the result */ public <T> T execute(VcLongCall<T> call) { calls.add(call); return call.waitForResult(); } }