I have a service method that does a considerable amount of looping and executing HTTP request for each loop.
So my intention is to use a seperate thread for this service. How can i correctly achieve this using ExecutorService.
private ExecutorService executorService;
#PostConstruct
public void init() {
executorService = Executors.newWorkStealingPool();
}
#Override
public ResponseDto getSummary() throws ExecutionException, InterruptedException {
Callable<ResponseDto> task = () -> {
try {
executeRequests();
log.debug("Fetching summary details ompleted");
} catch (Exception e) {
log.error("Exception occurred", e);
}
};
Future<ResponseDto> future = executorService.submit(task);
return future.get();
}
private ResponseDto executeRequests() {
// executing HTTP requests inside loops and returning ResponseDto
}
So upto now i have implemented like above. Im not sure about how to return the response when the work is done. Any suggestions?
I use a Timer.schedule() to periodically call the run() method of the TimerTask class to poll devices. Sometimes a MalformedJsonException or a IllegalStateException are thrown, which is processed in the catch block. The thread should continue to poll devices after handling the exception, but it stops.
When there are no errors, the run method is periodically called as expected.
I also tried calling the runModulesPoll() method from the catch block, but that didn't help.
private static void runModulesPoll(Boiler boiler) {
new Timer("Modules Poll Flow").schedule(new TimerTask() {
#Override
public void run() {
try {
Module[] modules = boiler.getCore().getModules();
for (Module module : modules) {
String response = ControllersService.sendMessage(MessageBuilder.buildDataRequest(module.getAlias(), boiler.getBoilerMode()));
if (AppUtils.isStringInvalid(response)) {
module.setOnline(false);
ModulesResetService.reset();
continue;
}
module.setOnline(true);
module.fromShortJson(response);
}
MqttService.publishMessage(MqttMessageFactory.createDataMessage(boiler.getCore().getModulesDataAsJson()));
} catch (Throwable e) {
LoggerLocal.error("Exception in Modules Poll Flow: " + e.getLocalizedMessage());
e.printStackTrace();
}
}
}, 0, 1);
}
According to the logs, the exception is handled as expected, but the thread does not continue polling.
14-11-2019 18:10:47 -- Exception in Modules Poll Flow: Not a JSON Object: "hgjhgjhg"
java.lang.IllegalStateException: Not a JSON Object: "hgjhgjhg"
at com.google.gson.JsonElement.getAsJsonObject(JsonElement.java:90)
at eezo.AppUtils.getJsonObjectFromString(AppUtils.java:101)
at eezo.services.ControllersService.handleIfErrorMessage(ControllersService.java:148)
at eezo.services.ControllersService.sendMessage(ControllersService.java:53)
at eezo.ApplicationRunner$1.run(ApplicationRunner.java:107)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
UPDATE:
Exception throws inside try block and handles in catch but thread stops without any other exceptions.
LoggerLocal doesn't produce exceptions at all.
I simulated the situation with a simpler example and everything works as expected, the thread does not fall and handles exceptions constantly.
private static void run(String[] args) {
final int[] i = {0};
new Timer("Modules Poll Flow").schedule(new TimerTask() {
#Override
public void run() {
try {
System.out.println("run() " + args);
if (i[0] == 5) throw new IllegalStateException("ssss");
i[0]++;
} catch (Exception e) {
System.out.println("Exception in Modules Poll Flow: " + e.getLocalizedMessage());
e.printStackTrace();
}
}
}, 0, 1);
}
In below code DataGather = endDataGather - beginDataGather takes 1.7ms
& time for service to respond = service_COMPLETED - service_REQUEST_SENT
which vary from 20us to 200 us(since they are mocked dummy on same lan hence so low)
now if i increase tomcat8 thread from 10 to 200,DataGather increase to 150ms + and even if I increase thread from 200 to 1000 then it even increase 250+.Machine specs 8 core Xenon,64gb ram. Time is measured when apache benchmark runs with -n 40000 -c 100 args , is this due to thread scheduling/context swtiching or something else? How do I get rid of this variation? Will it remain when real services will come into picture which have latency of 20-100ms.
public List<ServiceResponse> getData(final List<Service> services, final Data data) {
//beginDateGather;
final List<ServiceResponse> serviceResponses = Collections.synchronizedList(new ArrayList<>());
try {
final CountDownLatch latch = new CountDownLatch(services.size());
Map<Future<HttpResponse>, HttpRequestBase> responseRequestMap = new HashMap<Future<HttpResponse>, HttpRequestBase>();
for (final service service : services) {
//creating request for a service
try {
HttpRequestBase request = RequestCreator.getRequestBase(service, data);
//service_REQUEST_SENT
Future<HttpResponse> response = client.execute(request,
new MyFutureCallback(service, data, latch, serviceResponses));
responseRequestMap.put(response, request);
} catch (Exception e) {
latch.countDown();
}
}
try {
boolean isWaitIsOver = latch.await(timeout, TimeUnit.MILLISECONDS);
if (!isWaitIsOver) {
for (Future<HttpResponse> response : responseRequestMap.keySet()) {
if (!response.isDone()) {
response.cancel(true);
}
}
}
} catch (InterruptedException e) {
}
} catch (Exception e) {
}
//endDataGather
return serviceResponses;
}
public class MyFutureCallback implements FutureCallback<HttpResponse> {
private Service service;
private Data data;
private CountDownLatch latch;
private List<serviceResponse> serviceResponses;
public MyFutureCallback( Service service, Data data, CountDownLatch latch, List<ServiceResponse> serviceResponses) {
this.service = service;
this.data = data;
this.latch = latch;
this.serviceResponses = serviceResponses;
}
#Override
public void completed(HttpResponse result) {
try {
ServiceResponse serviceResponse = parseResponse(result, data, service);
serviceResponses.add(serviceResponse);
} catch (Exception e) {
} finally {
//service_COMPLETED
latch.countDown();
}
}
#Override
public void failed(Exception ex) {
latch.countDown();
}
#Override
public void cancelled() {
latch.countDown();
}
}
Yes it seems due to context switching of threads.
Increasing the number of threads won't help in this case.
You can use a thread pool for callbacks.
Check this link for your reference and try to use .PoolingClientAsyncConnectionManager
How to use HttpAsyncClient with multithreaded operation?
I have a server and several clients. The server should be able to delegate tasks to the clients so I tried to implement RMI. I followed this tutorial and everything is working fine if I use String as param- and/or return-value.
Now the server should send undefined tasks to the clients so I tried to use a Callable as param but the program crashed with a NotSerializableException. Since Callable doesn't implement the Serializeable interface thats the result I expected.
Now I found several sources that use Callable and Runnable as params and that confuses me. Is there any trick to get it to work? Or do i miss something important? Maybe theres a technology that fits better?
Resource1 S. 33
Resource2 s. 5
And heres my code:
// Client
public static void main(String[] args) throws InterruptedException {
App app = new App();
app.startClient();
Thread.sleep(20000);//just for test purpose
}
private void startClient() {
try {
// create on port 1099
Registry registry = LocateRegistry.createRegistry(1099);
// create a new service named myMessage
registry.rebind("calcClient", new CalculateRemoteImpl<String>());
} catch (RemoteException e) {
e.printStackTrace();
}
System.out.println("System is ready");
}
// RemoteInterface
public interface CalculateRemote<T> extends Remote {
public T hello(Callable<T> hello) throws RemoteException;
}
// RemoteInterfaceImpl
public class CalculateRemoteImpl<T> extends UnicastRemoteObject implements CalculateRemote<T> {
public T hello(Callable<T> hello) throws RemoteException {
return (T) ("Hello " + hello);// just print address of object
}
}
.
// Server
public static void main(String[] args) {
App app = new App();
app.doTest();
}
private void doTest() {
try {
// fire to localhost port 1099
Registry myRegistry = LocateRegistry.getRegistry("127.0.0.1", 1099);
// search for myMessage service
CalculateRemote<String> impl = (CalculateRemote<String>) myRegistry.lookup("calcClient");
// call server's method
System.out.println("Message: " + impl.hello(new Callable<String>() {
public String call() throws RemoteException, Exception {
return "hello";
}
}));
System.out.println("Message Sent");
} catch (NotBoundException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
// And the same RemoteInterface
public interface CalculateRemote<T> extends Remote {
public T hello(Callable<T> hello) throws RemoteException;
}
.
// stacktrace
java.rmi.MarshalException: error marshalling arguments; nested exception is:
java.io.NotSerializableException: de.fhb.rmicalcserver.App$1
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:156)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148)
at $Proxy0.hello(Unknown Source)
at de.fhb.rmicalcserver.App.doTest(App.java:30)
at de.fhb.rmicalcserver.App.main(App.java:18)
Caused by: java.io.NotSerializableException: de.fhb.rmicalcserver.App$1
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1180)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346)
at sun.rmi.server.UnicastRef.marshalValue(UnicastRef.java:292)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:151)
If you want to send objects to clients whose classes aren't deployed at the client you need to take a long look at the RMI codebase feature.
I'm trying to start a JMXConnectorServer for management and debug purposes. But I don't want this service to prevent application from exiting normally when the last non-daemon thread is terminated.
In other words, I want the following program to terminate immediately:
public class Main {
public static void main(final String[] args) throws IOException {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
JMXServiceURL jmxUrl = new JMXServiceURL("rmi", null, 0);
JMXConnectorServer connectorServer =
JMXConnectorServerFactory.newJMXConnectorServer(jmxUrl, null, mbs);
connectorServer.start();
}
}
I play with similar issue and wrote this class:
public final class HardDaemonizer extends Thread {
private final Runnable target;
private final String newThreadName;
public HardDaemonizer(Runnable target, String name, String newThreadName) {
super(name == null ? "Daemonizer" : name);
setDaemon(true);
this.target = target;
this.newThreadName = newThreadName;
}
#Override
public void run() {
try {
List<Thread> tb = getSubThreads();
target.run();
List<Thread> ta = new java.util.ArrayList<>(getSubThreads());
ta.removeAll(tb);
for (Thread thread : ta) {
thread.setName(newThreadName);
}
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException ex) {
Logger.getLogger(HardDaemonizer.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static Thread daemonize(String daemonizerName, String newThreadName, Runnable target) {
HardDaemonizer daemonizer = new HardDaemonizer(target, daemonizerName, newThreadName);
daemonizer.start();
return daemonizer;
}
private static List<Thread> getSubThreads() {
ThreadGroup group = Thread.currentThread().getThreadGroup().getParent();
Thread[] threads = new Thread[group.activeCount()];
group.enumerate(threads);
return java.util.Arrays.asList(threads);
}
}
You can use it in this way:
HardDaemonizer.daemonize(null, "ConnectorServer", new Runnable(){
#Override
public void run() {
try {
connectorServer.start();
} catch (IOException ex) {
Logger.getLogger(Ralph.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
Be careful - it's tricky!
EDIT
Agh... It's not solution for you. It hard-daemonize connector thread only and this thread will be killed when jvm stops. Additionaly you can customize name of this thread.
Alternatively you can add flag completed and sleep in loop in daemonize method until connector server start up.
SIMPLIFIED
This is simplified daemonizer without tricky thread renaming:
public abstract class Daemonizer<T> extends Thread {
private final T target;
private boolean completed = false;
private Exception cause = null;
public Daemonizer(T target) {
super(Daemonizer.class.getSimpleName());
setDaemon(true);
this.target = target;
}
#Override
public void run() {
try {
act(target);
} catch (Exception ex) {
cause = ex;
}
completed = true;
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException ex) {
java.util.logging.Logger.getLogger(Daemonizer.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
}
public abstract void act(final T target) throws Exception;
public static void daemonize(Daemonizer daemonizer) throws Exception {
daemonizer.start();
while (!daemonizer.completed) {
Thread.sleep(50);
}
if (daemonizer.cause != null) {
throw daemonizer.cause;
}
}
}
Usage:
Daemonizer.daemonize(new Daemonizer<JMXConnectorServer>(server) {
#Override
public void act(JMXConnectorServer server) throws Exception {
server.start();
}
});
Yeah, you will need to so a connectorServer.stop(); at some point.
Edit:
In reading your comments, it sounds like you should do something like:
connectorServer.start();
try {
// create thread-pool
ExecutorService threadPool = Executors...
// submit jobs to the thread-pool
...
threadPool.shutdown();
// wait for the submitted jobs to finish
threadPool.awaitTermination(Long.MAX_LONG, TimeUnit.SECONDS);
} finally {
connectorServer.stop();
}
#Nicholas' idea of the shutdown hook is a good one. Typically, however, I had my main thread wait on some sort of variable that is set from a shutdown() JMX operation. Something like:
public CountDownLatch shutdownLatch = new CountDownLatch(1);
...
// in main
connectorServer.start();
try {
// do the main-thread stuff
shutdownLatch.await();
} finally {
connectorServer.stop();
}
// in some JMX exposed operation
public void shutdown() {
Main.shutdownLatch.countDown();
}
As an aside, you could use my SimpleJMX package to manage your JMX server for you.
JmxServer jmxServer = new JmxServer(8000);
jmxServer.start();
try {
// register our lookupCache object defined below
jmxServer.register(lookupCache);
jmxServer.register(someOtherObject);
} finally {
jmxServer.stop();
}
From my experience, the JMXConnectorServer is only running in a user thread when you create it explicitly.
If you instead configure RMI access for the platform MBean server via system properties, the implicitly created JMX connector server will run as daemon process and not prevent the JMV shutdown. To do this, your code would shrink to the following
public class Main {
public static void main(final String[] args) throws IOException {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
}
}
but you'll need to set the following system properties:
-Dcom.sun.management.jmxremote.port=1919
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
You could add a JVM Shutdown Hook to stop the connector server.
===== UPDATE =====
Not sure why your shutdown hook doesn't work. Perhaps you can supply your sample code. Here's an example:
public static void main(String[] args) {
try {
log("Creating Connector Server");
final JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(new JMXServiceURL("rmi", "localhost", 12387), null, ManagementFactory.getPlatformMBeanServer());
Thread jcsStopper = new Thread("JCS-Stopper") {
public void run() {
if(jcs.isActive()) {
try {
jcs.stop();
log("Connector Server Stopped");
} catch (Exception e) {
log("Failed to stop JCS");
e.printStackTrace();
}
}
}
};
jcsStopper.setDaemon(false);
Runtime.getRuntime().addShutdownHook(jcsStopper);
log("Registered Server Stop Task");
jcs.start();
log("Server Started");
Thread.sleep(3000);
System.exit(0);
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
Output is:
[main]:Creating Connector Server
[main]:Registered Server Stop Task
[main]:Server Started
[JCS-Stopper]:Connector Server Stopped
String port = getProperty("com.sun.management.jmxremote.port");
if (port == null) {
port = String.valueOf(getAvailablePort());
System.setProperty("com.sun.management.jmxremote.port", port);
System.setProperty("com.sun.management.jmxremote.ssl", "false");
System.setProperty("com.sun.management.jmxremote.authenticate", "false");
sun.management.Agent.startAgent();
}
log.info(InetAddress.getLocalHost().getCanonicalHostName() + ":" + port);