Skip to content

Commit

Permalink
Added heartbeat mechanism to detect and free up idle/closed sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
fmichielssen committed Aug 3, 2016
1 parent bcc75e4 commit 1232e50
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/main/java/eu/openanalytics/controllers/AppController.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ String app(ModelMap map, Principal principal, HttpServletRequest request) {
map.put("title", environment.getProperty("shiny.proxy.title"));
map.put("logo", environment.getProperty("shiny.proxy.logo-url"));
map.put("container", "/" + mapping + environment.getProperty("shiny.proxy.landing-page"));
map.put("heartbeatRate", environment.getProperty("shiny.proxy.heartbeat-rate", "10000"));

return "app";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package eu.openanalytics.controllers;

import java.io.IOException;
import java.security.Principal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import eu.openanalytics.services.HeartbeatService;

@Controller
public class HeartbeatController {

@Inject
HeartbeatService heartbeatService;

@RequestMapping("/heartbeat/**")
void heartbeat(Principal principal, HttpServletRequest request, HttpServletResponse response) {
String userName = (principal == null) ? request.getSession().getId() : principal.getName();
Matcher matcher = Pattern.compile(".*/app/(.*)").matcher(request.getRequestURI());
String appName = matcher.matches() ? matcher.group(1) : null;
heartbeatService.heartbeatReceived(userName, appName);
try {
response.setStatus(200);
response.getWriter().write("Ok");
response.getWriter().flush();
} catch (IOException e) {}
}
}
3 changes: 3 additions & 0 deletions src/main/java/eu/openanalytics/services/DockerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public static class Proxy {
public String containerId;
public String userName;
public String appName;
public long startupTimestamp;
}

@Bean
Expand Down Expand Up @@ -119,6 +120,7 @@ public List<Proxy> listProxies() {
copy.containerId = proxy.containerId;
copy.userName = proxy.userName;
copy.appName = proxy.appName;
copy.startupTimestamp = proxy.startupTimestamp;
proxies.add(copy);
}
}
Expand Down Expand Up @@ -218,6 +220,7 @@ private Proxy startProxy(String userName, String appName) {
ContainerInfo info = dockerClient.inspectContainer(container.id());
proxy.name = info.name().substring(1);
proxy.containerId = container.id();
proxy.startupTimestamp = System.currentTimeMillis();
} catch (Exception e) {
releasePort(proxy.port);
throw new ShinyProxyException("Failed to start container: " + e.getMessage(), e);
Expand Down
66 changes: 66 additions & 0 deletions src/main/java/eu/openanalytics/services/HeartbeatService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package eu.openanalytics.services;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.log4j.Logger;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import eu.openanalytics.services.DockerService.Proxy;

@Service
public class HeartbeatService {

@Inject
Environment environment;

@Inject
DockerService dockerService;

private Logger log = Logger.getLogger(HeartbeatService.class);

private Map<String, Long> heartbeatTimestamps;

@PostConstruct
public void init() {
heartbeatTimestamps = new ConcurrentHashMap<>();
new Thread(new AppCleaner(), "HeartbeatThread").start();
}

public void heartbeatReceived(String user, String app) {
heartbeatTimestamps.put(user, System.currentTimeMillis());
}

private class AppCleaner implements Runnable {
@Override
public void run() {
long cleanupInterval = 2 * Long.parseLong(environment.getProperty("shiny.proxy.heartbeat-rate", "10000"));
long heartbeatTimeout = Long.parseLong(environment.getProperty("shiny.proxy.heartbeat-timeout", "60000"));

while (true) {
try {
long currentTimestamp = System.currentTimeMillis();
for (Proxy proxy: dockerService.listProxies()) {
Long lastHeartbeat = heartbeatTimestamps.get(proxy.userName);
if (lastHeartbeat == null) lastHeartbeat = proxy.startupTimestamp;
long proxySilence = currentTimestamp - lastHeartbeat;
if (proxySilence > heartbeatTimeout) {
log.info(String.format("Releasing inactive proxy [user: %s] [app: %s] [silence: %dms]", proxy.userName, proxy.appName, proxySilence));
dockerService.releaseProxy(proxy.userName);
heartbeatTimestamps.remove(proxy.userName);
}
}
} catch (Throwable t) {
log.error("Error in HeartbeatThread", t);
}
try {
Thread.sleep(cleanupInterval);
} catch (InterruptedException e) {}
}
}
}
}
2 changes: 2 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ shiny:
title: Open Analytics Shiny Proxy
logo-url: http://www.openanalytics.eu/sites/www.openanalytics.eu/themes/oa/logo.png
landing-page: /
heartbeat-rate: 10000
heartbeat-timeout: 60000
port: 8080
authentication: ldap
# LDAP configuration
Expand Down
11 changes: 10 additions & 1 deletion src/main/resources/templates/app.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,17 @@

<!-- content -->
<iframe id="shinyframe" th:src="${container}" width="100%" style="margin-top: 50px; border: 0px;"></iframe>
<script type="text/javascript">
<script type="text/javascript" th:inline="javascript">
$('#shinyframe').css('height', ($(window).height()-50)+'px');

function heartbeat() {
setTimeout(function() {
$.ajax("/heartbeat" + window.location.pathname).success(function(data) {
heartbeat();
});
}, [[${heartbeatRate}]]);
};
heartbeat();
</script>
</body>
</html>

0 comments on commit 1232e50

Please sign in to comment.