| /***************************************************************************** |
| * Copyright (c) 2016, 2019 CEA LIST. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * CEA LIST Initial API and implementation |
| * CEA LIST - Bug 551906 |
| * |
| *****************************************************************************/ |
| package org.eclipse.papyrus.moka.debug.service; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.locks.Condition; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.paho.client.mqttv3.MqttException; |
| import org.eclipse.papyrus.moka.debug.engine.IDebuggableExecutionEngine; |
| import org.eclipse.papyrus.moka.debug.engine.IDebuggableExecutionEngineThread; |
| import org.eclipse.papyrus.moka.kernel.process.MQTTServerConfig; |
| import org.eclipse.papyrus.moka.kernel.service.ExecutionEngineService; |
| |
| public abstract class DebugService<T, C> extends ExecutionEngineService<IDebuggableExecutionEngine<T, C>> |
| implements IDebugService<T, C> { |
| |
| /** |
| * Client enabling the debug service to pass messages to the debug target (if |
| * any) |
| */ |
| protected DebugServiceClient client; |
| |
| /** |
| * Flag specifying the need for the execution engine to be suspended |
| */ |
| protected boolean enginSuspendRequest; |
| |
| /** |
| * Lock ensuring safe access and update of suspension flag |
| */ |
| protected ReentrantLock engineSuspendRequestLock; |
| |
| /** |
| * Flag specifying the need for the execution engine to be terminated |
| */ |
| protected boolean engineTerminateRequest; |
| |
| /** |
| * Lock ensuring safe access and update of the terminate flag |
| */ |
| protected ReentrantLock engineTerminateRequestLock; |
| |
| /** |
| * Map specifying the need for the execution engine thread to be suspended |
| */ |
| protected Map<String, Boolean> threadSuspendRequest; |
| |
| /** |
| * Lock ensuring safe access and update to the suspend requests |
| */ |
| protected ReentrantLock threadSuspendRequestLock; |
| |
| /** |
| * List specifying the need for execution engine thread to be terminated |
| */ |
| protected List<String> threadTerminateRequest; |
| |
| /** |
| * Lock ensuring safe access and update to the terminate requests |
| */ |
| protected ReentrantLock threadTerminateRequestLock; |
| |
| protected ReentrantLock engineLock; |
| |
| protected Condition engineExecutionCondition; |
| |
| @Override |
| public void init(IDebuggableExecutionEngine<T, C> engine) { |
| super.init(engine); |
| engineLock = new ReentrantLock(true); |
| engineExecutionCondition = engineLock.newCondition(); |
| enginSuspendRequest = false; |
| engineSuspendRequestLock = new ReentrantLock(true); |
| engineTerminateRequest = false; |
| engineTerminateRequestLock = new ReentrantLock(); |
| threadSuspendRequest = new HashMap<String, Boolean>(); |
| threadSuspendRequestLock = new ReentrantLock(true); |
| threadTerminateRequest = new ArrayList<String>(); |
| threadTerminateRequestLock = new ReentrantLock(true); |
| initClient(); |
| initDebugAssistant(); |
| } |
| |
| private void initClient() { |
| String port = MQTTServerConfig.getMQTTServerPort(); |
| client = new DebugServiceClient("tcp://localhost:" + port, "Debug Service Client", this); |
| client.run(); |
| } |
| |
| protected abstract void initDebugAssistant(); |
| |
| protected boolean shouldEngineSuspend() { |
| boolean flag = false; |
| engineSuspendRequestLock.lock(); |
| flag = enginSuspendRequest; |
| engineSuspendRequestLock.unlock(); |
| return flag; |
| } |
| |
| protected boolean shouldEngineTerminate() { |
| boolean flag = false; |
| engineTerminateRequestLock.lock(); |
| flag = engineTerminateRequest; |
| engineTerminateRequestLock.unlock(); |
| return flag; |
| } |
| |
| @Override |
| public void dispose(IDebuggableExecutionEngine<T, C> engine) { |
| client.terminate(); |
| if (client.isConnected()) { |
| try { |
| client.disconnect(); |
| } catch (MqttException e) { |
| e.printStackTrace(); |
| } |
| } |
| client = null; |
| } |
| |
| /** |
| * Request the execution engine suspension |
| * |
| * This operations intended to be called by the debug service client listener |
| * thread. |
| */ |
| @Override |
| public boolean requestSuspendEngine() { |
| engineSuspendRequestLock.lock(); |
| enginSuspendRequest = true; |
| engineSuspendRequestLock.unlock(); |
| return true; |
| } |
| |
| /** |
| * Suspend the execution engine that is attached to this service |
| * |
| * This operation is intended to be called by the thread executing the execution |
| * engine. This thread will become suspended when it executes |
| * {@code engineExecutionCondition.await()} |
| */ |
| public void suspendEngine() { |
| if (engine.canSuspend()) { |
| engineSuspendRequestLock.lock(); |
| enginSuspendRequest = false; |
| engineSuspendRequestLock.unlock(); |
| if (engine.canSuspend()) { |
| try { |
| engine.suspend(); |
| } catch (DebugException e) { |
| e.printStackTrace(); |
| } finally { |
| engineLock.lock(); |
| while (engine.isSuspended()) { |
| try { |
| engineExecutionCondition.await(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } finally { |
| engineLock.unlock(); |
| } |
| } |
| engineLock.unlock(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Request the execution engine to be resumed |
| * |
| * This operations intended to be called by the debug service client listener |
| * thread. Invocation of {engineExecutionCondition.signalAll()} will resume the |
| * execution engine thread. |
| */ |
| @Override |
| public boolean requestResumeEngine() { |
| if (engine.canResume()) { |
| try { |
| engine.resume(); |
| } catch (DebugException e) { |
| e.printStackTrace(); |
| } finally { |
| engineLock.lock(); |
| engineExecutionCondition.signalAll(); |
| engineLock.unlock(); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Request the execution engine termination |
| * |
| * This operations intended to be called by the debug service client listener |
| * thread. |
| */ |
| @Override |
| public boolean requestTerminateEngine() { |
| engineTerminateRequestLock.lock(); |
| engineTerminateRequest = true; |
| engineTerminateRequestLock.unlock(); |
| if (engine.isSuspended()) { |
| requestResumeEngine(); |
| } |
| return true; |
| } |
| |
| /** |
| * Terminate the execution engine. |
| * |
| * This operation is intended to be called by the thread executing the execution |
| * engine. This thread will become suspended when it executes |
| * {@code engine.terminate()} |
| */ |
| public void terminateEngine() { |
| if (engine.canTerminate()) { |
| engineTerminateRequestLock.lock(); |
| engineTerminateRequest = false; |
| engineTerminateRequestLock.unlock(); |
| try { |
| engine.terminate(); |
| } catch (DebugException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| /** |
| * Request the suspension of a specific thread. |
| * |
| * @param identifier the thread identifier |
| * |
| * This operations intended to be called by the debug service |
| * client listener thread. |
| */ |
| @Override |
| public boolean requestSuspendThread(String identifier) { |
| threadSuspendRequestLock.lock(); |
| threadSuspendRequest.put(identifier, true); |
| threadSuspendRequestLock.unlock(); |
| return true; |
| } |
| |
| /** |
| * Suspend the thread in the execution engine |
| * |
| * @param thread the thread to be suspended |
| * |
| * This operation is intended to be called by the thread executing |
| * the execution engine. This thread will be suspended. |
| */ |
| public void suspendThread(IDebuggableExecutionEngineThread<T, C> thread) { |
| engine.suspendThread(thread); |
| if (engine.canSuspend()) { |
| engineLock.lock(); |
| try { |
| // Note - The execution engine has a single |
| // execution thread. Hence, when a suspension |
| // request is made for a one of the logical thread, |
| // then the overall execution engine is suspended |
| engineExecutionCondition.await(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } finally { |
| engineLock.unlock(); |
| } |
| } |
| } |
| |
| /** |
| * Request a specific thread to be resumed |
| * |
| * @param identifier the thread identifier |
| * |
| * This operations intended to be called by the debug service |
| * client listener thread. |
| */ |
| @Override |
| public boolean requestResumeThread(String identifier) { |
| boolean resumed = false; |
| IDebuggableExecutionEngineThread<T, C> thread = engine.getThread(identifier); |
| if (thread != null) { |
| boolean shouldEngineResume = false; |
| engine.resumeThread(thread); |
| threadSuspendRequestLock.lock(); |
| threadSuspendRequest.put(thread.getID(), false); |
| shouldEngineResume = shouldEngineResume(threadSuspendRequest); |
| threadSuspendRequestLock.unlock(); |
| resumed = true; |
| if (shouldEngineResume) { |
| engineLock.lock(); |
| engineExecutionCondition.signalAll(); |
| engineLock.unlock(); |
| } |
| } |
| return resumed; |
| } |
| |
| private static boolean shouldEngineResume(final Map<String, Boolean> suspendRequest) { |
| return suspendRequest.entrySet().stream().noneMatch((e) -> e.getValue() == true); |
| } |
| |
| /** |
| * Request a specific thread to be terminated |
| * |
| * @param identifier the thread identifier |
| * |
| * This operations intended to be called by the debug service |
| * client listener thread. |
| */ |
| @Override |
| public boolean requestTerminateThread(String identifier) { |
| boolean added = false; |
| threadTerminateRequestLock.lock(); |
| if (!threadTerminateRequest.contains(identifier)) { |
| added = threadTerminateRequest.add(identifier); |
| } |
| threadTerminateRequestLock.unlock(); |
| return added; |
| } |
| |
| /** |
| * Terminate the thread in the execution engine |
| * |
| * @param thread the thread to be terminated |
| * |
| * This operation is intended to be called by the thread executing |
| * the execution engine. This thread will not be suspended. |
| */ |
| public void terminateThread(IDebuggableExecutionEngineThread<T, C> thread) { |
| threadTerminateRequestLock.lock(); |
| if (threadTerminateRequest.contains(thread.getID())) { |
| threadTerminateRequest.remove(thread.getID()); |
| } |
| threadTerminateRequestLock.unlock(); |
| engine.terminateThread(thread); |
| } |
| |
| @Override |
| public void fireTerminateEngineEvent() { |
| client.fireTerminateEngineEvent(); |
| } |
| |
| } |