public void QueueOp(AssetOp op) { if (_threadAborted) { throw new Exception("Thread has already been aborted for this op manager!"); } op.SetStatus(OpStatus.Queued); _opQueue.Enqueue(op); OpStatusChanged?.Invoke(this, op); lock (_threadLock) { if (_thread != null && !_thread.IsAlive) { Log.LogErr("Thread in opmanager was stopped but not null!"); try { _thread.Abort(); _opQueue.Where(x => x.Status == OpStatus.Started).ToList().ForEach(x => { x.SetStatus(OpStatus.Failed, new Exception("Op was in a Started state when the thread died. Failing it.")); Log.LogErr("Op was found in a started state while the thread had died!"); }); _thread = null; } catch { } } if (_thread == null) { _thread = new Thread(WorkerThreadProc); _thread.Start(); } } _queueEvent.Set(); }
private void OpManager_OpStatusChanged(object sender, QuestomAssets.AssetOps.AssetOp e) { List <AssetOp> opCopy; lock (_trackedOps) { if (!_trackedOps.Any(x => x.ID == e.ID)) { _trackedOps.Add(e); } _trackedOps.RemoveAll(x => x.Status == OpStatus.Complete); opCopy = _trackedOps.ToList(); } HostOpStatus opstat = new HostOpStatus(); foreach (var op in opCopy) { string exmsg = null; //if (op.Exception != null) //{ // exmsg = $"{op.Exception.Message} {op.Exception.StackTrace}"; // var ex = op.Exception.InnerException; // while (ex != null) // { // exmsg += $"\nInnerException: {ex.Message} {ex.StackTrace}"; // ex = ex.InnerException; // } //} opstat.Ops.Add(new HostOp() { ID = op.ID, OpDescription = op.GetType().Name, Status = op.Status, Error = op.Exception?.Message }); } lock (_sendClientOpsChangedLock) { if (_sendClientOpsChanged == null) { _sendClientOpsChanged = new Debouncey <HostOpStatus>(400, false); _sendClientOpsChanged.Debounced += (e2, a) => { SendMessageToClient(a); }; } } if (e.Status == OpStatus.Complete && e.IsWriteOp) { CurrentConfig.IsCommitted = CurrentConfig.IsCommitted && (!Engine.HasChanges); SendConfigChangeMessage(); } _sendClientOpsChanged.EventRaised(this, opstat); }
private void OpManager_OpStatusChanged(object sender, QuestomAssets.AssetOps.AssetOp e) { List <AssetOp> opCopy; lock (_trackedOps) { if (!_trackedOps.Any(x => x.ID == e.ID)) { _trackedOps.Add(e); } _trackedOps.RemoveAll(x => x.Status == OpStatus.Complete); opCopy = _trackedOps.ToList(); } HostOpStatus opstat = new HostOpStatus(); foreach (var op in opCopy) { string exmsg = null; //if (op.Exception != null) //{ // exmsg = $"{op.Exception.Message} {op.Exception.StackTrace}"; // var ex = op.Exception.InnerException; // while (ex != null) // { // exmsg += $"\nInnerException: {ex.Message} {ex.StackTrace}"; // ex = ex.InnerException; // } //} opstat.Ops.Add(new HostOp() { ID = op.ID, OpDescription = op.GetType().Name, Status = op.Status, Error = op.Exception?.Message }); } lock (_sendClientOpsChangedLock) { if (_sendClientOpsChanged == null) { _sendClientOpsChanged = new Debouncey <HostOpStatus>(400, false); _sendClientOpsChanged.Debounced += (e2, a) => { lock (lastSendOpsLock) { lastOpsSendHadOps = (a.Ops.Count > 0); SendMessageToClient(a); } }; } } if (e.Status == OpStatus.Complete && e.IsWriteOp) { CurrentConfig.IsCommitted = CurrentConfig.IsCommitted && (!Engine.HasChanges); SendConfigChangeMessage(); } //if the last send didn't have ops and this one does, send the message immediately in addition to debouncing it. // this will send a duplicate message because the debounce is called in addition to the message being sent, but it'll assure keeping things in sync with the client lock (lastSendOpsLock) { if ((opstat.Ops.Count > 0) && !lastOpsSendHadOps) { lastOpsSendHadOps = true; SendMessageToClient(opstat); } } _sendClientOpsChanged.EventRaised(this, opstat); }
private void WorkerThreadProc(object o) { bool entered = false; //I *think* this covers all the race conditions where the thread would stop as an item was being enqueued, probably need to re-review try { do { if (_threadAborted) { Log.LogMsg("Opmanager worker thread has been aborted"); return; } AssetOp op = null; bool dequeued = false; bool threadAborting = false; dequeued = _opQueue.TryDequeue(out op); if (!dequeued) { if (entered) { Log.LogMsg("trying to exit on thread " + Thread.CurrentThread.ManagedThreadId); Monitor.Exit(_context.Manager); entered = false; } _queueEvent.WaitOne(2000); continue; } if (!entered) { entered = Monitor.TryEnter(_context.Manager); if (!entered) { continue; } Log.LogMsg("entered on thread " + Thread.CurrentThread.ManagedThreadId); } try { Stopwatch sw = new Stopwatch(); op.SetStatus(OpStatus.Started); OpStatusChanged?.Invoke(this, op); Log.LogMsg($"AssetOpManager starting op of type {op.GetType().Name}"); sw.Start(); op.PerformOp(_context); sw.Stop(); Log.LogMsg($"AssetOpManager completed op of type {op.GetType().Name} in {sw.ElapsedMilliseconds}ms"); op.SetStatus(OpStatus.Complete); OpStatusChanged?.Invoke(this, op); } catch (ThreadAbortException) { //just die if being aborted _thread = null; threadAborting = true; Log.LogMsg("Op thread aborting"); return; } catch (Exception ex) { Log.LogErr($"Error handling Op type {op.GetType()}, operation threw an exception.", ex); op.SetStatus(OpStatus.Failed, ex); } finally { if (!threadAborting) { OpStatusChanged?.Invoke(this, op); } } } while (true); } catch (ThreadAbortException) { //Thread aborting, so let it Log.LogErr("Op manager thread is aborting"); _thread = null; return; } catch (Exception ex) { Log.LogErr("Exception in op manager's worker thread proc!", ex); } finally { if (entered) { Monitor.Exit(_context.Manager); } //just to make sure it gets set null in all cases _thread = null; } }