/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt VS Tools. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace QtVsTools.Qml.Debug.V4 { interface IMessageEventSink { bool QueryRuntimeFrozen(); void NotifyStateTransition( DebugClient client, DebugClientState oldState, DebugClientState newState); void NotifyRequestResponded(Request req); void NotifyEvent(Event evt); void NotifyMessage(Message msg); } class ProtocolDriver : Finalizable, IConnectionEventSink { IMessageEventSink sink; DebugClient client; int nextRequestSeq = 0; Dictionary pendingRequests = new Dictionary(); Task eventHandlingThread; EventWaitHandle eventReceived = new EventWaitHandle(false, EventResetMode.AutoReset); ConcurrentQueue eventQueue = new ConcurrentQueue(); public uint? ThreadId { get { return client.ThreadId; } } public static ProtocolDriver Create(IMessageEventSink sink) { var _this = new ProtocolDriver(); return _this.Initialize(sink) ? _this : null; } private ProtocolDriver() { } private bool Initialize(IMessageEventSink sink) { this.sink = sink; eventHandlingThread = Task.Run(() => EventHandlingThread()); client = DebugClient.Create(this); if (client == null) return false; return true; } protected override void DisposeManaged() { foreach (var req in ThreadSafe(() => pendingRequests.Values.ToList())) req.Dispose(); ThreadSafe(() => pendingRequests.Clear()); client.Dispose(); } protected override void DisposeFinally() { eventReceived.Set(); eventHandlingThread.Wait(); eventReceived.Dispose(); } private void EventHandlingThread() { while (!Disposing) { eventReceived.WaitOne(); Event evt; while (!Disposing && eventQueue.TryDequeue(out evt)) sink.NotifyEvent(evt); } } public DebugClientState ConnectionState { get { return client.State; } } bool IConnectionEventSink.QueryRuntimeFrozen() { return sink.QueryRuntimeFrozen(); } public EventWaitHandle Connect(string hostName, ushort hostPort) { return client.Connect(hostName, hostPort); } public EventWaitHandle StartLocalServer(string fileName) { return client.StartLocalServer(fileName); } public bool Disconnect() { return client.Disconnect(); } public bool SendMessage(Message message) { if (client.State != DebugClientState.Connected) return false; var messageParams = message.Serialize(); System.Diagnostics.Debug .Assert(messageParams != null, "Empty message data"); return client.SendMessage(message.Type, messageParams); } public PendingRequest SendRequest(Request request) { ThreadSafe(() => request.SequenceNum = nextRequestSeq++); var pendingRequest = new PendingRequest(request); ThreadSafe(() => pendingRequests[request.SequenceNum] = pendingRequest); if (!SendMessage(request as Message)) { ThreadSafe(() => pendingRequests.Remove(request.SequenceNum)); pendingRequest.Dispose(); return new PendingRequest(); } return pendingRequest; } void IConnectionEventSink.NotifyStateTransition( DebugClient client, DebugClientState oldState, DebugClientState newState) { sink.NotifyStateTransition(client, oldState, newState); } void IConnectionEventSink.NotifyMessageReceived( DebugClient client, string messageType, byte[] messageParams) { if (client != this.client || Disposing) return; var msg = Message.Deserialize(messageType, messageParams); if (msg == null) return; if (msg is Response) { var msgResponse = msg as Response; EnterCriticalSection(); PendingRequest pendingRequest = null; if (pendingRequests.TryGetValue(msgResponse.RequestSeq, out pendingRequest)) { pendingRequests.Remove(msgResponse.RequestSeq); LeaveCriticalSection(); pendingRequest.SetResponse(msgResponse); sink.NotifyRequestResponded(pendingRequest.Request); pendingRequest.Dispose(); } else { LeaveCriticalSection(); sink.NotifyMessage(msg); } } else if (msg is Event) { var msgEvent = msg as Event; eventQueue.Enqueue(msgEvent); eventReceived.Set(); } else { sink.NotifyMessage(msg); } } #region //////////////////// PendingRequest /////////////////////////////////////////////// public class PendingRequest : Finalizable { public Request Request { get; private set; } EventWaitHandle responded; public PendingRequest() { Request = null; responded = null; } public PendingRequest(Request req) { responded = new EventWaitHandle(false, EventResetMode.ManualReset); Request = req; } public bool RequestSent { get { return Request != null; } } public bool ResponseReceived { get { return Request != null && Request.Response != null; } } public Response WaitForResponse() { if (Request == null) return null; if (responded != null) responded.WaitOne(); return Request.Response; } protected override void DisposeManaged() { if (Request == null) return; if (responded != null) responded.Dispose(); } public void SetResponse(Response res) { if (Request == null || Disposing) return; Request.Response = res; if (responded != null) responded.Set(); } } #endregion //////////////////// PendingRequest //////////////////////////////////////////// } }