NIHVIVO-3209 Create two controllers: one will show the status of all living instances of VitroBackgroundThread (for system monitoring); the other will wait until they are idle (for Selenium tests).
This commit is contained in:
parent
e3db0778cb
commit
b4796c649a
4 changed files with 332 additions and 0 deletions
|
@ -0,0 +1,97 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.webapp.controller.admin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.ibm.icu.text.SimpleDateFormat;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;
|
||||
|
||||
/**
|
||||
* Show the list of living background threads (instances of
|
||||
* VitroBackgroundThread), and their status.
|
||||
*/
|
||||
public class ShowBackgroundThreadsController extends FreemarkerHttpServlet {
|
||||
|
||||
private static final String TEMPLATE_NAME = "admin-showThreads.ftl";
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
|
||||
@Override
|
||||
protected ResponseValues processRequest(VitroRequest vreq) throws Exception {
|
||||
|
||||
SortedMap<String, ThreadInfo> threadMap = new TreeMap<String, ThreadInfo>();
|
||||
|
||||
for (VitroBackgroundThread thread : VitroBackgroundThread
|
||||
.getLivingThreads()) {
|
||||
ThreadInfo threadInfo = getThreadInfo(thread);
|
||||
threadMap.put(threadInfo.getName(), threadInfo);
|
||||
}
|
||||
|
||||
Map<String, Object> bodyMap = new HashMap<String, Object>();
|
||||
bodyMap.put("threads", new ArrayList<ThreadInfo>(threadMap.values()));
|
||||
|
||||
return new TemplateResponseValues(TEMPLATE_NAME, bodyMap);
|
||||
|
||||
}
|
||||
|
||||
private ThreadInfo getThreadInfo(VitroBackgroundThread thread) {
|
||||
try {
|
||||
String name = thread.getName();
|
||||
String workLevel = String.valueOf(thread.getWorkLevel().getLevel());
|
||||
String since = formatDate(thread.getWorkLevel().getSince());
|
||||
String flags = String.valueOf(thread.getWorkLevel().getFlags());
|
||||
return new ThreadInfo(name, workLevel, since, flags);
|
||||
} catch (Exception e) {
|
||||
return new ThreadInfo("UNKNOWN THREAD", "UNKNOWN", "UNKNOWN",
|
||||
e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String formatDate(Date since) {
|
||||
return new SimpleDateFormat(DATE_FORMAT).format(since);
|
||||
}
|
||||
|
||||
|
||||
public static class ThreadInfo {
|
||||
private final String name;
|
||||
private final String workLevel;
|
||||
private final String since;
|
||||
private final String flags;
|
||||
|
||||
public ThreadInfo(String name, String workLevel, String since,
|
||||
String flags) {
|
||||
this.name = name;
|
||||
this.workLevel = workLevel;
|
||||
this.since = since;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getWorkLevel() {
|
||||
return workLevel;
|
||||
}
|
||||
|
||||
public String getSince() {
|
||||
return since;
|
||||
}
|
||||
|
||||
public String getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||
|
||||
package edu.cornell.mannlib.vitro.webapp.controller.admin;
|
||||
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_TEMPORARY_REDIRECT;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevel;
|
||||
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevelStamp;
|
||||
|
||||
/**
|
||||
* Wait for background threads to complete. Used in Selenium testing.
|
||||
*
|
||||
* This servlet will poll background threads (instances of
|
||||
* VitroBackgroundThread) until all living threads are idle, or until a maximum
|
||||
* wait time has been met. The wait time can be specified with a "waitLimit"
|
||||
* parameter on the request (in seconds), or the default value will be used.
|
||||
*
|
||||
* If the maximum time expires before all threads become idle, the result will
|
||||
* be 503 (Service Unavailable)
|
||||
*
|
||||
* Else if a "return" parameter exists, and a "referer" header exists, the
|
||||
* result will be a 307 (Temporary Redirect) back to the referer URL.
|
||||
*
|
||||
* Otherwise, the result will be 200 (OK), with a brief message.
|
||||
*/
|
||||
public class WaitForBackgroundThreadsController extends VitroHttpServlet {
|
||||
private static final Log log = LogFactory
|
||||
.getLog(WaitForBackgroundThreadsController.class);
|
||||
|
||||
private static final String PARAMETER_WAIT_LIMIT = "waitLimit";
|
||||
private static final String PARAMETER_RETURN = "return";
|
||||
private static final String HEADER_REFERER = "Referer";
|
||||
private static final String HEADER_LOCATION = "Location";
|
||||
private static final int DEFAULT_WAIT_LIMIT_VALUE = 30;
|
||||
private static final int POLLING_INTERVAL = 3;
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws IOException, ServletException {
|
||||
|
||||
int maximumWait = figureMaximumWait(req);
|
||||
String redirect = figureRedirect(req);
|
||||
|
||||
Collection<String> remainingThreads = waitForThreads(maximumWait);
|
||||
if (remainingThreads.isEmpty()) {
|
||||
if (redirect == null) {
|
||||
sendOK(resp);
|
||||
} else {
|
||||
sendRedirect(resp, redirect);
|
||||
}
|
||||
} else {
|
||||
sendFailure(resp, maximumWait, remainingThreads);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the "waitLimit " parameter is present and is set to a non-negative
|
||||
* integer, use that as the maximum number of seconds. Otherwise, use the
|
||||
* default limit.
|
||||
*/
|
||||
private int figureMaximumWait(HttpServletRequest req) {
|
||||
String valueString = req.getParameter(PARAMETER_WAIT_LIMIT);
|
||||
if (valueString == null) {
|
||||
return DEFAULT_WAIT_LIMIT_VALUE;
|
||||
}
|
||||
|
||||
int value;
|
||||
try {
|
||||
value = Integer.parseInt(valueString);
|
||||
} catch (NumberFormatException e) {
|
||||
return DEFAULT_WAIT_LIMIT_VALUE;
|
||||
}
|
||||
|
||||
if (value <= 0) {
|
||||
return DEFAULT_WAIT_LIMIT_VALUE;
|
||||
}
|
||||
|
||||
log.debug("Maximum wait time (seconds): " + value);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a "return" parameter and a "referer" header, return the
|
||||
* referer URL. Otherwise, return null.
|
||||
*/
|
||||
private String figureRedirect(HttpServletRequest req) {
|
||||
if (!req.getParameterMap().containsKey(PARAMETER_RETURN)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Enumeration<?> referers = req.getHeaders(HEADER_REFERER);
|
||||
if ((referers == null) || (!referers.hasMoreElements())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String redirect = (String) referers.nextElement();
|
||||
log.debug("Redirect is to '" + redirect + "'");
|
||||
return redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until all background threads have become idle, or until the time
|
||||
* limit is passed. Return the names of any that are still active; hopefully
|
||||
* an empty list.
|
||||
*/
|
||||
private Collection<String> waitForThreads(int maximumWait) {
|
||||
int elapsedSeconds = 0;
|
||||
|
||||
while (true) {
|
||||
Collection<String> threadNames = getNamesOfBusyThreads();
|
||||
if (threadNames.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
try {
|
||||
log.debug("Waiting for " + POLLING_INTERVAL + " seconds.");
|
||||
Thread.sleep(POLLING_INTERVAL * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
// Why would this happen? Anyway, stop waiting.
|
||||
return Collections.singleton("Polling was interrupted");
|
||||
}
|
||||
|
||||
elapsedSeconds += POLLING_INTERVAL;
|
||||
if (elapsedSeconds >= maximumWait) {
|
||||
return threadNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<String> getNamesOfBusyThreads() {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (VitroBackgroundThread thread : VitroBackgroundThread.getThreads()) {
|
||||
if (thread.isAlive()) {
|
||||
WorkLevelStamp stamp = thread.getWorkLevel();
|
||||
if ((stamp != null) && (stamp.getLevel() == WorkLevel.WORKING)) {
|
||||
names.add(thread.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("Busy threads: " + names);
|
||||
return names;
|
||||
}
|
||||
|
||||
private void sendOK(HttpServletResponse resp) throws IOException {
|
||||
log.debug("All threads are idle");
|
||||
resp.setStatus(SC_OK);
|
||||
resp.getWriter().println(
|
||||
"<html><body>All threads are idle.</body></html>");
|
||||
}
|
||||
|
||||
private void sendRedirect(HttpServletResponse resp, String redirect) {
|
||||
log.debug("All threads are idle. Redirecting to '" + redirect + "'");
|
||||
resp.setStatus(SC_TEMPORARY_REDIRECT);
|
||||
resp.setHeader(HEADER_LOCATION, redirect);
|
||||
}
|
||||
|
||||
private void sendFailure(HttpServletResponse resp, int maximumWait,
|
||||
Collection<String> namesOfBusyThreads) throws IOException {
|
||||
log.debug("Timeout after " + maximumWait
|
||||
+ " seconds with busy threads: " + namesOfBusyThreads);
|
||||
resp.setStatus(SC_SERVICE_UNAVAILABLE);
|
||||
resp.getWriter().println(
|
||||
"<html><body>After " + maximumWait + " seconds, "
|
||||
+ namesOfBusyThreads.size()
|
||||
+ " threads are still busy: " + namesOfBusyThreads
|
||||
+ "</body></html>");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue