Best Coyote code snippet using Microsoft.Coyote.Runtime.ControlledOperation.Dispose
CoyoteRuntime.cs
Source:CoyoteRuntime.cs
...639 try640 {641 pendingOp.Value.Wait();642 }643 catch (ObjectDisposedException)644 {645 // The handler was disposed, so we can ignore this exception.646 }647 }648 IO.Debug.WriteLine("[coyote::debug] Waking up thread '{0}'.", Thread.CurrentThread.ManagedThreadId);649 }650 pendingOp.Value.Dispose();651 this.PendingStartOperationMap.Remove(pendingOp.Key);652 }653 }654 }655 /// <summary>656 /// Pauses the execution of the specified operation.657 /// </summary>658 /// <remarks>659 /// It is assumed that this method is invoked by the same thread executing the operation660 /// and that it runs in the scope of a <see cref="SynchronizedSection"/>.661 /// </remarks>662 private void PauseOperation(ControlledOperation op)663 {664 // Only pause the operation if it is not already completed and it is currently executing on this thread.665 if (op.Status != OperationStatus.Completed && op == ExecutingOperation.Value)666 {667 // Do not allow the operation to wake up, unless its currently scheduled and enabled or the runtime stopped running.668 while (!(op == this.ScheduledOperation && op.Status is OperationStatus.Enabled) && this.ExecutionStatus is ExecutionStatus.Running)669 {670 IO.Debug.WriteLine("[coyote::debug] Sleeping operation '{0}' of group '{1}' on thread '{2}'.",671 op.Name, op.Group, Thread.CurrentThread.ManagedThreadId);672 using (SynchronizedSection.Exit(this.RuntimeLock))673 {674 op.WaitSignal();675 }676 IO.Debug.WriteLine("[coyote::debug] Waking up operation '{0}' of group '{1}' on thread '{2}'.",677 op.Name, op.Group, Thread.CurrentThread.ManagedThreadId);678 }679 }680 }681 /// <summary>682 /// Pauses the currently executing operation until the specified condition gets resolved.683 /// </summary>684 internal void PauseOperationUntil(ControlledOperation current, Func<bool> condition, bool isConditionControlled = true, string debugMsg = null)685 {686 using (SynchronizedSection.Enter(this.RuntimeLock))687 {688 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)689 {690 // Only proceed if there is an operation executing on the current thread and691 // the condition is not already resolved.692 current ??= this.GetExecutingOperation();693 while (current != null && !condition() && this.ExecutionStatus is ExecutionStatus.Running)694 {695 IO.Debug.WriteLine("[coyote::debug] Operation '{0}' of group '{1}' is waiting for {2} on thread '{3}'.",696 current.Name, current.Group, debugMsg ?? "condition to get resolved", Thread.CurrentThread.ManagedThreadId);697 // TODO: can we identify when the dependency is uncontrolled?698 current.PauseWithDependency(condition, isConditionControlled);699 this.ScheduleNextOperation(current, SchedulingPointType.Pause);700 }701 }702 }703 }704 /// <summary>705 /// Asynchronously pauses the currently executing operation until the specified condition gets resolved.706 /// </summary>707 internal PausedOperationAwaitable PauseOperationUntilAsync(Func<bool> condition, bool resumeAsynchronously)708 {709 using (SynchronizedSection.Enter(this.RuntimeLock))710 {711 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving &&712 this.TryGetExecutingOperation(out ControlledOperation current))713 {714 return new PausedOperationAwaitable(this, current, condition, resumeAsynchronously);715 }716 }717 return new PausedOperationAwaitable(this, null, condition, resumeAsynchronously);718 }719 /// <summary>720 /// Schedules the next enabled operation, which can include the currently executing operation.721 /// </summary>722 /// <param name="current">The currently executing operation, if there is one.</param>723 /// <param name="type">The type of the scheduling point.</param>724 /// <param name="isSuppressible">True if the interleaving can be suppressed, else false.</param>725 /// <param name="isYielding">True if the current operation is yielding, else false.</param>726 /// <remarks>727 /// An enabled operation is one that is not paused nor completed.728 /// </remarks>729 internal void ScheduleNextOperation(ControlledOperation current, SchedulingPointType type, bool isSuppressible = true, bool isYielding = false)730 {731 using (SynchronizedSection.Enter(this.RuntimeLock))732 {733 // Wait for all recently created operations to start executing.734 this.WaitOperationsStart();735 if (this.ExecutionStatus != ExecutionStatus.Running ||736 this.SchedulingPolicy != SchedulingPolicy.Interleaving)737 {738 // Cannot schedule the next operation if the scheduler is not attached,739 // or if the scheduling policy is not systematic.740 return;741 }742 // Check if the currently executing thread is uncontrolled.743 bool isThreadUncontrolled = false;744 if (current is null && !this.IsThreadControlled(Thread.CurrentThread))745 {746 if (this.LastPostponedSchedulingPoint is SchedulingPointType.Pause ||747 this.LastPostponedSchedulingPoint is SchedulingPointType.Complete)748 {749 // A scheduling point was postponed due to a potential deadlock, which has750 // now been resolved, so resume it on this uncontrolled thread.751 current = this.ScheduledOperation;752 type = this.LastPostponedSchedulingPoint.Value;753 IO.Debug.WriteLine("[coyote::debug] Resuming scheduling point '{0}' of operation '{1}' in uncontrolled thread '{2}'.",754 type, current, Thread.CurrentThread.ManagedThreadId);755 }756 else if (type is SchedulingPointType.Create || type is SchedulingPointType.ContinueWith)757 {758 // This is a scheduling point that was invoked because a new operation was759 // created by an uncontrolled thread, so postpone the scheduling point and760 // resume it on the next available controlled thread.761 IO.Debug.WriteLine("[coyote::debug] Postponing scheduling point '{0}' in uncontrolled thread '{1}'.",762 type, Thread.CurrentThread.ManagedThreadId);763 this.LastPostponedSchedulingPoint = type;764 return;765 }766 isThreadUncontrolled = true;767 }768 // If the current operation was provided as argument to this method, or it is null, then this769 // is a controlled thread, so get the currently executing operation to proceed with scheduling.770 current ??= this.GetExecutingOperation();771 if (current is null)772 {773 // Cannot proceed without having access to the currently executing operation.774 return;775 }776 if (current != this.ScheduledOperation)777 {778 // The currently executing operation is not scheduled, so send it to sleep.779 this.PauseOperation(current);780 return;781 }782 if (this.IsSchedulerSuppressed && this.LastPostponedSchedulingPoint is null &&783 isSuppressible && current.Status is OperationStatus.Enabled)784 {785 // Suppress the scheduling point.786 IO.Debug.WriteLine("[coyote::debug] Suppressing scheduling point in operation '{0}'.", current.Name);787 return;788 }789 this.Assert(!this.IsSpecificationInvoked, "Executing a specification monitor must be atomic.");790 // Checks if the scheduling steps bound has been reached.791 this.CheckIfSchedulingStepsBoundIsReached();792 // Update metadata related to this scheduling point.793 current.LastSchedulingPoint = type;794 this.LastPostponedSchedulingPoint = null;795 if (this.Configuration.IsProgramStateHashingEnabled)796 {797 // Update the current operation with the hashed program state.798 current.LastHashedProgramState = this.GetHashedProgramState();799 }800 // Try to enable any operations with resolved dependencies before asking the801 // scheduler to choose the next one to schedule.802 IEnumerable<ControlledOperation> ops = this.OperationMap.Values;803 if (!this.TryEnableOperationsWithResolvedDependencies(current))804 {805 if (this.IsUncontrolledConcurrencyDetected &&806 this.Configuration.IsPartiallyControlledConcurrencyAllowed)807 {808 // TODO: optimize and make this more fine-grained.809 // If uncontrolled concurrency is detected, then do not check for deadlocks directly,810 // but instead leave it to the background deadlock detection timer and postpone the811 // scheduling point, which might get resolved from an uncontrolled thread.812 IO.Debug.WriteLine("[coyote::debug] Postponing scheduling point '{0}' of operation '{1}' due to potential deadlock.",813 type, current);814 this.LastPostponedSchedulingPoint = type;815 this.PauseOperation(current);816 return;817 }818 // Check if the execution has deadlocked.819 this.CheckIfExecutionHasDeadlocked(ops);820 }821 if (this.Configuration.IsLivenessCheckingEnabled && this.Scheduler.IsScheduleFair)822 {823 // Check if the liveness threshold has been reached if scheduling is fair.824 this.CheckLivenessThresholdExceeded();825 }826 if (!this.Scheduler.GetNextOperation(ops, current, isYielding, out ControlledOperation next))827 {828 // The scheduler hit the scheduling steps bound.829 this.Detach(ExecutionStatus.BoundReached);830 }831 IO.Debug.WriteLine("[coyote::debug] Scheduling operation '{0}' of group '{1}'.", next.Name, next.Group);832 if (current != next)833 {834 // Pause the currently scheduled operation, and enable the next one.835 this.ScheduledOperation = next;836 next.Signal();837 this.PauseOperation(current);838 }839 else if (isThreadUncontrolled)840 {841 // If the current operation is the next operation to schedule, and the current thread842 // is uncontrolled, then we need to signal the current operation to resume execution.843 next.Signal();844 }845 }846 }847 /// <summary>848 /// Delays the currently executing operation for a non-deterministically chosen amount of time.849 /// </summary>850 /// <remarks>851 /// The delay is chosen non-deterministically by an underlying fuzzing strategy.852 /// If a delay of 0 is chosen, then the operation is not delayed.853 /// </remarks>854 internal void DelayOperation(ControlledOperation current)855 {856 using (SynchronizedSection.Enter(this.RuntimeLock))857 {858 if (this.ExecutionStatus != ExecutionStatus.Running)859 {860 throw new ThreadInterruptedException();861 }862 if (current != null || this.TryGetExecutingOperation(out current))863 {864 // Choose the next delay to inject. The value is in milliseconds.865 int delay = this.GetNondeterministicDelay(current, (int)this.Configuration.MaxFuzzingDelay);866 IO.Debug.WriteLine("[coyote::debug] Delaying operation '{0}' on thread '{1}' by {2}ms.",867 current.Name, Thread.CurrentThread.ManagedThreadId, delay);868 // Only sleep the executing operation if a non-zero delay was chosen.869 if (delay > 0)870 {871 var previousStatus = current.Status;872 current.Status = OperationStatus.PausedOnDelay;873 using (SynchronizedSection.Exit(this.RuntimeLock))874 {875 Thread.SpinWait(delay);876 }877 current.Status = previousStatus;878 }879 }880 }881 }882 /// <summary>883 /// Completes the specified operation.884 /// </summary>885 internal void CompleteOperation(ControlledOperation op)886 {887 op.ExecuteContinuations();888 using (SynchronizedSection.Enter(this.RuntimeLock))889 {890 IO.Debug.WriteLine("[coyote::debug] Completed operation '{0}' of group '{1}' on thread '{2}'.",891 op.Name, op.Group, Thread.CurrentThread.ManagedThreadId);892 op.Status = OperationStatus.Completed;893 }894 }895 /// <summary>896 /// Tries to reset the specified controlled operation so that it can start executing again.897 /// This is only allowed if the operation is already completed.898 /// </summary>899 /// <param name="op">The operation to reset.</param>900 internal bool TryResetOperation(ControlledOperation op)901 {902 using (SynchronizedSection.Enter(this.RuntimeLock))903 {904 if (op.Status is OperationStatus.Completed)905 {906 IO.Debug.WriteLine("[coyote::debug] Resetting operation '{0}' of group '{1}' from thread '{2}'.",907 op.Name, op.Group, Thread.CurrentThread.ManagedThreadId);908 op.Status = OperationStatus.None;909 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)910 {911 // Assign an event handler so that the next scheduling decision cannot be912 // made until this operation starts executing to avoid race conditions.913 this.PendingStartOperationMap.Add(op, new ManualResetEventSlim(false));914 }915 return true;916 }917 }918 return false;919 }920 /// <summary>921 /// Suppresses scheduling points until <see cref="ResumeScheduling"/> is invoked,922 /// unless a scheduling point must occur naturally.923 /// </summary>924 internal void SuppressScheduling()925 {926 using (SynchronizedSection.Enter(this.RuntimeLock))927 {928 IO.Debug.WriteLine("[coyote::debug] Suppressing scheduling of enabled operations in runtime '{0}'.", this.Id);929 this.IsSchedulerSuppressed = true;930 }931 }932 /// <summary>933 /// Resumes scheduling points that were suppressed by invoking <see cref="SuppressScheduling"/>.934 /// </summary>935 internal void ResumeScheduling()936 {937 using (SynchronizedSection.Enter(this.RuntimeLock))938 {939 IO.Debug.WriteLine("[coyote::debug] Resuming scheduling of enabled operations in runtime '{0}'.", this.Id);940 this.IsSchedulerSuppressed = false;941 }942 }943 /// <summary>944 /// Sets a checkpoint in the currently explored execution trace, that allows replaying all945 /// scheduling decisions until the checkpoint in subsequent iterations.946 /// </summary>947 internal void CheckpointExecutionTrace()948 {949 using (SynchronizedSection.Enter(this.RuntimeLock))950 {951 ExecutionTrace trace = this.Scheduler.CheckpointExecutionTrace();952 IO.Debug.WriteLine("[coyote::debug] Set checkpoint in current execution path with length '{0}' in runtime '{1}'.",953 trace.Length, this.Id);954 }955 }956 /// <inheritdoc/>957 public bool RandomBoolean() => this.GetNextNondeterministicBooleanChoice(null, null);958 /// <summary>959 /// Returns the next nondeterministic boolean choice.960 /// </summary>961 internal bool GetNextNondeterministicBooleanChoice(string callerName, string callerType)962 {963 using (SynchronizedSection.Enter(this.RuntimeLock))964 {965 bool result;966 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)967 {968 // Checks if the current operation is controlled by the runtime.969 this.GetExecutingOperation();970 // Checks if the scheduling steps bound has been reached.971 this.CheckIfSchedulingStepsBoundIsReached();972 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving &&973 this.Configuration.IsLivenessCheckingEnabled && this.Scheduler.IsScheduleFair)974 {975 // Check if the liveness threshold has been reached if scheduling is fair.976 this.CheckLivenessThresholdExceeded();977 }978 if (this.Configuration.IsProgramStateHashingEnabled)979 {980 // Update the current operation with the hashed program state.981 this.ScheduledOperation.LastHashedProgramState = this.GetHashedProgramState();982 }983 if (!this.Scheduler.GetNextBoolean(this.ScheduledOperation, out result))984 {985 this.Detach(ExecutionStatus.BoundReached);986 }987 }988 else989 {990 result = this.ValueGenerator.Next(2) is 0 ? true : false;991 }992 this.LogWriter.LogRandom(result, callerName, callerType);993 return result;994 }995 }996 /// <inheritdoc/>997 public int RandomInteger(int maxValue) => this.GetNextNondeterministicIntegerChoice(maxValue, null, null);998 /// <summary>999 /// Returns the next nondeterministic integer choice.1000 /// </summary>1001 internal int GetNextNondeterministicIntegerChoice(int maxValue, string callerName, string callerType)1002 {1003 using (SynchronizedSection.Enter(this.RuntimeLock))1004 {1005 int result;1006 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)1007 {1008 // Checks if the current operation is controlled by the runtime.1009 this.GetExecutingOperation();1010 // Checks if the scheduling steps bound has been reached.1011 this.CheckIfSchedulingStepsBoundIsReached();1012 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving &&1013 this.Configuration.IsLivenessCheckingEnabled && this.Scheduler.IsScheduleFair)1014 {1015 // Check if the liveness threshold has been reached if scheduling is fair.1016 this.CheckLivenessThresholdExceeded();1017 }1018 if (this.Configuration.IsProgramStateHashingEnabled)1019 {1020 // Update the current operation with the hashed program state.1021 this.ScheduledOperation.LastHashedProgramState = this.GetHashedProgramState();1022 }1023 if (!this.Scheduler.GetNextInteger(this.ScheduledOperation, maxValue, out result))1024 {1025 this.Detach(ExecutionStatus.BoundReached);1026 }1027 }1028 else1029 {1030 result = this.ValueGenerator.Next(maxValue);1031 }1032 this.LogWriter.LogRandom(result, callerName, callerType);1033 return result;1034 }1035 }1036 /// <summary>1037 /// Returns a controlled nondeterministic delay for the specified operation.1038 /// </summary>1039 private int GetNondeterministicDelay(ControlledOperation op, int maxDelay)1040 {1041 using (SynchronizedSection.Enter(this.RuntimeLock))1042 {1043 // Checks if the scheduling steps bound has been reached.1044 this.CheckIfSchedulingStepsBoundIsReached();1045 // Choose the next delay to inject.1046 if (!this.Scheduler.GetNextDelay(this.OperationMap.Values, op, maxDelay, out int next))1047 {1048 this.Detach(ExecutionStatus.BoundReached);1049 }1050 return next;1051 }1052 }1053 /// <summary>1054 /// Tries to enable any operations that have their dependencies resolved. It returns1055 /// true if there is at least one operation enabled, else false.1056 /// </summary>1057 /// <remarks>1058 /// It is assumed that this method runs in the scope of a <see cref="SynchronizedSection"/>.1059 /// </remarks>1060 private bool TryEnableOperationsWithResolvedDependencies(ControlledOperation current)1061 {1062 IO.Debug.WriteLine("[coyote::debug] Trying to enable any operation with resolved dependencies in runtime '{0}'.", this.Id);1063 int attempt = 0;1064 int delay = (int)this.Configuration.UncontrolledConcurrencyResolutionDelay;1065 uint maxAttempts = this.Configuration.UncontrolledConcurrencyResolutionAttempts;1066 uint enabledOpsCount = 0;1067 while (true)1068 {1069 // Cache the count of enabled operations from the previous attempt.1070 uint previousEnabledOpsCount = enabledOpsCount;1071 enabledOpsCount = 0;1072 uint statusChanges = 0;1073 bool isRootDependencyUnresolved = false;1074 bool isAnyDependencyUnresolved = false;1075 foreach (var op in this.OperationMap.Values)1076 {1077 var previousStatus = op.Status;1078 if (op.IsPaused)1079 {1080 this.TryEnableOperation(op);1081 if (previousStatus == op.Status)1082 {1083 IO.Debug.WriteLine("[coyote::debug] Operation '{0}' of group '{1}' has status '{2}'.",1084 op.Name, op.Group, op.Status);1085 if (op.IsPaused && op.IsDependencyUncontrolled)1086 {1087 if (op.IsRoot)1088 {1089 isRootDependencyUnresolved = true;1090 }1091 else1092 {1093 isAnyDependencyUnresolved = true;1094 }1095 }1096 }1097 else1098 {1099 IO.Debug.WriteLine("[coyote::debug] Operation '{0}' of group '{1}' changed status from '{2}' to '{3}'.",1100 op.Name, op.Group, previousStatus, op.Status);1101 statusChanges++;1102 }1103 }1104 if (op.Status is OperationStatus.Enabled)1105 {1106 enabledOpsCount++;1107 }1108 }1109 // Heuristics for handling a partially controlled execution.1110 if (this.IsUncontrolledConcurrencyDetected &&1111 this.Configuration.IsPartiallyControlledConcurrencyAllowed)1112 {1113 // Compute the delta of enabled operations from the previous attempt.1114 uint enabledOpsDelta = attempt is 0 ? 0 : enabledOpsCount - previousEnabledOpsCount;1115 // This value is true if the current operation just completed and has uncontrolled source.1116 bool isSourceUnresolved = current.Status is OperationStatus.Completed && current.IsSourceUncontrolled;1117 // We consider the concurrency to be unresolved if there were no new enabled operations1118 // or status changes in this attempt, and one of the following cases holds:1119 // - If there are no enabled operations, then the concurrency is unresolved if1120 // the current operation was just completed and has uncontrolled source, or1121 // if there are any unresolved dependencies.1122 // - If there are enabled operations, then the concurrency is unresolved if1123 // there are any (non-root) unresolved dependencies.1124 bool isNoEnabledOpsCaseResolved = enabledOpsCount is 0 &&1125 (isSourceUnresolved || isAnyDependencyUnresolved || isRootDependencyUnresolved);1126 bool isSomeEnabledOpsCaseResolved = enabledOpsCount > 0 && isAnyDependencyUnresolved;1127 bool isConcurrencyUnresolved = enabledOpsDelta is 0 && statusChanges is 0 &&1128 (isNoEnabledOpsCaseResolved || isSomeEnabledOpsCaseResolved);1129 // Retry if there is unresolved concurrency and attempts left.1130 if (++attempt < maxAttempts && isConcurrencyUnresolved)1131 {1132 // Implement a simple retry logic to try resolve uncontrolled concurrency.1133 IO.Debug.WriteLine("[coyote::debug] Pausing controlled thread '{0}' to try resolve uncontrolled concurrency.",1134 Thread.CurrentThread.ManagedThreadId);1135 using (SynchronizedSection.Exit(this.RuntimeLock))1136 {1137 Thread.SpinWait(delay);1138 }1139 continue;1140 }1141 }1142 break;1143 }1144 IO.Debug.WriteLine("[coyote::debug] There are {0} enabled operations in runtime '{1}'.", enabledOpsCount, this.Id);1145 this.MaxConcurrencyDegree = Math.Max(this.MaxConcurrencyDegree, enabledOpsCount);1146 return enabledOpsCount > 0;1147 }1148 /// <summary>1149 /// Tries to enable the specified operation, if its dependencies have been resolved.1150 /// </summary>1151 /// <remarks>1152 /// It is assumed that this method runs in the scope of a <see cref="SynchronizedSection"/>.1153 /// </remarks>1154 private bool TryEnableOperation(ControlledOperation op)1155 {1156 if (op.Status is OperationStatus.PausedOnDelay && op is DelayOperation delayedOp)1157 {1158 if (delayedOp.Delay > 0)1159 {1160 delayedOp.Delay--;1161 }1162 // The operation is delayed, so it is enabled either if the delay completes1163 // or if no other operation is enabled.1164 if (delayedOp.Delay is 0 ||1165 !this.OperationMap.Any(kvp => kvp.Value.Status is OperationStatus.Enabled))1166 {1167 delayedOp.Delay = 0;1168 delayedOp.Status = OperationStatus.Enabled;1169 return true;1170 }1171 return false;1172 }1173 // If the operation is paused, then check if its dependency has been resolved.1174 return op.TryEnable();1175 }1176 /// <summary>1177 /// Pauses the scheduled controlled operation until either the uncontrolled task completes,1178 /// it tries to invoke an uncontrolled scheduling point, or the timeout expires.1179 /// </summary>1180 /// <remarks>1181 /// It is assumed that this method runs in the scope of a <see cref="SynchronizedSection"/>.1182 /// </remarks>1183 private void TryPauseAndResolveUncontrolledTask(Task task)1184 {1185 if (this.IsThreadControlled(Thread.CurrentThread))1186 {1187 // A scheduling point from an uncontrolled thread has not been postponed yet, so pause the execution1188 // of the current operation to try give time to the uncontrolled concurrency to be resolved.1189 if (this.LastPostponedSchedulingPoint is null)1190 {1191 int attempt = 0;1192 int delay = (int)this.Configuration.UncontrolledConcurrencyResolutionDelay;1193 uint maxAttempts = this.Configuration.UncontrolledConcurrencyResolutionAttempts;1194 while (attempt++ < maxAttempts && !task.IsCompleted)1195 {1196 IO.Debug.WriteLine("[coyote::debug] Pausing controlled thread '{0}' to try resolve uncontrolled concurrency.",1197 Thread.CurrentThread.ManagedThreadId);1198 using (SynchronizedSection.Exit(this.RuntimeLock))1199 {1200 Thread.SpinWait(delay);1201 }1202 if (this.LastPostponedSchedulingPoint.HasValue)1203 {1204 // A scheduling point from an uncontrolled thread has been postponed,1205 // so stop trying to resolve the uncontrolled concurrency.1206 break;1207 }1208 }1209 }1210 if (this.LastPostponedSchedulingPoint.HasValue)1211 {1212 IO.Debug.WriteLine("[coyote::debug] Resuming controlled thread '{0}' with uncontrolled concurrency resolved.",1213 Thread.CurrentThread.ManagedThreadId);1214 this.ScheduleNextOperation(default, this.LastPostponedSchedulingPoint.Value, isSuppressible: false);1215 }1216 }1217 }1218 /// <summary>1219 /// Returns the currently executing <see cref="ControlledOperation"/>,1220 /// or null if no such operation is executing.1221 /// </summary>1222 internal ControlledOperation GetExecutingOperation()1223 {1224 using (SynchronizedSection.Enter(this.RuntimeLock))1225 {1226 var op = ExecutingOperation.Value;1227 if (op is null)1228 {1229 this.NotifyUncontrolledCurrentThread();1230 }1231 return op;1232 }1233 }1234 /// <summary>1235 /// Returns the currently executing <see cref="ControlledOperation"/> of the1236 /// specified type, or null if no such operation is executing.1237 /// </summary>1238 internal TControlledOperation GetExecutingOperation<TControlledOperation>()1239 where TControlledOperation : ControlledOperation1240 {1241 using (SynchronizedSection.Enter(this.RuntimeLock))1242 {1243 var op = ExecutingOperation.Value;1244 if (op is null)1245 {1246 this.NotifyUncontrolledCurrentThread();1247 }1248 return op is TControlledOperation expected ? expected : default;1249 }1250 }1251 /// <summary>1252 /// Tries to return the currently executing <see cref="ControlledOperation"/>,1253 /// or false if no such operation is executing.1254 /// </summary>1255 internal bool TryGetExecutingOperation(out ControlledOperation op)1256 {1257 op = this.GetExecutingOperation();1258 return op != null;1259 }1260 /// <summary>1261 /// Returns the <see cref="ControlledOperation"/> associated with the specified1262 /// operation id, or null if no such operation exists.1263 /// </summary>1264 internal ControlledOperation GetOperationWithId(ulong operationId)1265 {1266 using (SynchronizedSection.Enter(this.RuntimeLock))1267 {1268 this.OperationMap.TryGetValue(operationId, out ControlledOperation op);1269 return op;1270 }1271 }1272 /// <summary>1273 /// Returns the <see cref="ControlledOperation"/> of the specified type that is associated1274 /// with the specified operation id, or null if no such operation exists.1275 /// </summary>1276 internal TControlledOperation GetOperationWithId<TControlledOperation>(ulong operationId)1277 where TControlledOperation : ControlledOperation1278 {1279 using (SynchronizedSection.Enter(this.RuntimeLock))1280 {1281 if (this.OperationMap.TryGetValue(operationId, out ControlledOperation op) &&1282 op is TControlledOperation expected)1283 {1284 return expected;1285 }1286 }1287 return default;1288 }1289 /// <summary>1290 /// Returns all registered operations.1291 /// </summary>1292 /// <remarks>1293 /// This operation is thread safe because the systematic testing1294 /// runtime serializes the execution.1295 /// </remarks>1296 private IEnumerable<ControlledOperation> GetRegisteredOperations()1297 {1298 using (SynchronizedSection.Enter(this.RuntimeLock))1299 {1300 return this.OperationMap.Values;1301 }1302 }1303 /// <summary>1304 /// Returns the next available unique operation id.1305 /// </summary>1306 /// <returns>Value representing the next available unique operation id.</returns>1307 internal ulong GetNextOperationId() =>1308 // Atomically increments and safely wraps the value into an unsigned long.1309 (ulong)Interlocked.Increment(ref this.OperationIdCounter) - 1;1310 /// <summary>1311 /// Returns the current hashed state of the execution.1312 /// </summary>1313 /// <remarks>1314 /// The hash is updated in each execution step.1315 /// </remarks>1316 private int GetHashedProgramState()1317 {1318 unchecked1319 {1320 int hash = 19;1321 foreach (var operation in this.GetRegisteredOperations().OrderBy(op => op.Id))1322 {1323 if (operation is ActorOperation actorOperation)1324 {1325 int operationHash = 31 + actorOperation.Actor.GetHashedState(this.SchedulingPolicy);1326 operationHash = (operationHash * 31) + actorOperation.LastSchedulingPoint.GetHashCode();1327 hash *= operationHash;1328 }1329 else1330 {1331 hash *= 31 + operation.LastSchedulingPoint.GetHashCode();1332 }1333 }1334 foreach (var monitor in this.SpecificationMonitors)1335 {1336 hash *= 31 + monitor.GetHashedState();1337 }1338 return hash;1339 }1340 }1341 /// <inheritdoc/>1342 public void RegisterMonitor<T>()1343 where T : Specifications.Monitor =>1344 this.TryCreateMonitor(typeof(T));1345 /// <summary>1346 /// Tries to create a new <see cref="Specifications.Monitor"/> of the specified <see cref="Type"/>.1347 /// </summary>1348 private bool TryCreateMonitor(Type type)1349 {1350 if (this.SchedulingPolicy != SchedulingPolicy.None ||1351 this.Configuration.IsMonitoringEnabledInInProduction)1352 {1353 using (SynchronizedSection.Enter(this.RuntimeLock))1354 {1355 // Only one monitor per type is allowed.1356 if (!this.SpecificationMonitors.Any(m => m.GetType() == type))1357 {1358 var monitor = (Specifications.Monitor)Activator.CreateInstance(type);1359 monitor.Initialize(this.Configuration, this, this.LogWriter);1360 monitor.InitializeStateInformation();1361 this.SpecificationMonitors.Add(monitor);1362 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)1363 {1364 this.SuppressScheduling();1365 this.IsSpecificationInvoked = true;1366 monitor.GotoStartState();1367 this.IsSpecificationInvoked = false;1368 this.ResumeScheduling();1369 }1370 else1371 {1372 monitor.GotoStartState();1373 }1374 return true;1375 }1376 }1377 }1378 return false;1379 }1380 /// <inheritdoc/>1381 public void Monitor<T>(Event e)1382 where T : Specifications.Monitor =>1383 this.InvokeMonitor(typeof(T), e, null, null, null);1384 /// <summary>1385 /// Invokes the specified <see cref="Specifications.Monitor"/> with the specified <see cref="Event"/>.1386 /// </summary>1387 internal void InvokeMonitor(Type type, Event e, string senderName, string senderType, string senderStateName)1388 {1389 if (this.SchedulingPolicy != SchedulingPolicy.None ||1390 this.Configuration.IsMonitoringEnabledInInProduction)1391 {1392 using (SynchronizedSection.Enter(this.RuntimeLock))1393 {1394 Specifications.Monitor monitor = null;1395 foreach (var m in this.SpecificationMonitors)1396 {1397 if (m.GetType() == type)1398 {1399 monitor = m;1400 break;1401 }1402 }1403 if (monitor != null)1404 {1405 this.Assert(e != null, "Cannot invoke monitor '{0}' with a null event.", type.FullName);1406 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)1407 {1408 this.SuppressScheduling();1409 this.IsSpecificationInvoked = true;1410 monitor.MonitorEvent(e, senderName, senderType, senderStateName);1411 this.IsSpecificationInvoked = false;1412 this.ResumeScheduling();1413 }1414 else1415 {1416 monitor.MonitorEvent(e, senderName, senderType, senderStateName);1417 }1418 }1419 }1420 }1421 }1422 /// <inheritdoc/>1423 public void Assert(bool predicate)1424 {1425 if (!predicate)1426 {1427 string msg = "Detected an assertion failure.";1428 if (this.SchedulingPolicy is SchedulingPolicy.None)1429 {1430 throw new AssertionFailureException(msg);1431 }1432 this.NotifyAssertionFailure(msg);1433 }1434 }1435 /// <inheritdoc/>1436 public void Assert(bool predicate, string s, object arg0)1437 {1438 if (!predicate)1439 {1440 var msg = string.Format(CultureInfo.InvariantCulture, s, arg0?.ToString());1441 if (this.SchedulingPolicy is SchedulingPolicy.None)1442 {1443 throw new AssertionFailureException(msg);1444 }1445 this.NotifyAssertionFailure(msg);1446 }1447 }1448 /// <inheritdoc/>1449 public void Assert(bool predicate, string s, object arg0, object arg1)1450 {1451 if (!predicate)1452 {1453 var msg = string.Format(CultureInfo.InvariantCulture, s, arg0?.ToString(), arg1?.ToString());1454 if (this.SchedulingPolicy is SchedulingPolicy.None)1455 {1456 throw new AssertionFailureException(msg);1457 }1458 this.NotifyAssertionFailure(msg);1459 }1460 }1461 /// <inheritdoc/>1462 public void Assert(bool predicate, string s, object arg0, object arg1, object arg2)1463 {1464 if (!predicate)1465 {1466 var msg = string.Format(CultureInfo.InvariantCulture, s, arg0?.ToString(), arg1?.ToString(), arg2?.ToString());1467 if (this.SchedulingPolicy is SchedulingPolicy.None)1468 {1469 throw new AssertionFailureException(msg);1470 }1471 this.NotifyAssertionFailure(msg);1472 }1473 }1474 /// <inheritdoc/>1475 public void Assert(bool predicate, string s, params object[] args)1476 {1477 if (!predicate)1478 {1479 var msg = string.Format(CultureInfo.InvariantCulture, s, args);1480 if (this.SchedulingPolicy is SchedulingPolicy.None)1481 {1482 throw new AssertionFailureException(msg);1483 }1484 this.NotifyAssertionFailure(msg);1485 }1486 }1487 /// <summary>1488 /// Creates a liveness monitor that checks if the specified task eventually completes execution successfully.1489 /// </summary>1490 internal void MonitorTaskCompletion(Task task)1491 {1492 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving &&1493 task.Status != TaskStatus.RanToCompletion)1494 {1495 var monitor = new TaskLivenessMonitor(task);1496 this.TaskLivenessMonitors.Add(monitor);1497 }1498 }1499 /// <summary>1500 /// Starts running a background monitor that checks for potential deadlocks.1501 /// </summary>1502 private void StartMonitoringDeadlocks() => Task.Factory.StartNew(this.CheckIfExecutionHasDeadlockedAsync,1503 this.CancellationSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);1504 /// <summary>1505 /// Returns true if the specified thread is controlled, else false.1506 /// </summary>1507 private bool IsThreadControlled(Thread thread)1508 {1509 string name = thread?.Name;1510 return name != null && this.ControlledThreads.ContainsKey(name);1511 }1512 /// <summary>1513 /// Returns true if the specified task is uncontrolled, else false.1514 /// </summary>1515 internal bool IsTaskUncontrolled(Task task) =>1516 task != null && !task.IsCompleted && !this.ControlledTasks.ContainsKey(task);1517 /// <summary>1518 /// Checks if the awaited task is uncontrolled.1519 /// </summary>1520 internal bool CheckIfAwaitedTaskIsUncontrolled(Task task)1521 {1522 if (this.IsTaskUncontrolled(task))1523 {1524 this.NotifyUncontrolledTaskWait(task);1525 return true;1526 }1527 return false;1528 }1529 /// <summary>1530 /// Checks if the task returned from the specified method is uncontrolled.1531 /// </summary>1532 internal bool CheckIfReturnedTaskIsUncontrolled(Task task, string methodName)1533 {1534 if (this.IsTaskUncontrolled(task))1535 {1536 this.NotifyUncontrolledTaskReturned(task, methodName);1537 return true;1538 }1539 return false;1540 }1541 /// <summary>1542 /// Checks if the execution has deadlocked. This happens when there are no more enabled operations,1543 /// but there is one or more paused operations that are waiting some resource to complete.1544 /// </summary>1545 private void CheckIfExecutionHasDeadlocked(IEnumerable<ControlledOperation> ops)1546 {1547 if (ops.Any(op => op.Status is OperationStatus.Enabled))1548 {1549 // There are still enabled operations, so the execution is not deadlocked.1550 return;1551 }1552 var pausedOperations = ops.Where(op => op.Status is OperationStatus.Paused).ToList();1553 var pausedOnResources = ops.Where(op => op.Status is OperationStatus.PausedOnResource).ToList();1554 var pausedOnReceiveOperations = ops.Where(op => op.Status is OperationStatus.PausedOnReceive).ToList();1555 var totalCount = pausedOperations.Count + pausedOnResources.Count + pausedOnReceiveOperations.Count;1556 if (totalCount is 0)1557 {1558 // There are no paused operations, so the execution is not deadlocked.1559 return;1560 }1561 // To simplify the error message, remove the root operation, unless it is the only one that is paused.1562 if (totalCount > 1)1563 {1564 pausedOperations.RemoveAll(op => op.IsRoot);1565 pausedOnResources.RemoveAll(op => op.IsRoot);1566 pausedOnReceiveOperations.RemoveAll(op => op.IsRoot);1567 }1568 StringBuilder msg;1569 if (this.IsUncontrolledConcurrencyDetected)1570 {1571 msg = new StringBuilder("Potential deadlock detected.");1572 }1573 else1574 {1575 msg = new StringBuilder("Deadlock detected.");1576 }1577 if (pausedOperations.Count > 0)1578 {1579 for (int idx = 0; idx < pausedOperations.Count; idx++)1580 {1581 msg.Append(string.Format(CultureInfo.InvariantCulture, " {0}", pausedOperations[idx].Name));1582 if (idx == pausedOperations.Count - 2)1583 {1584 msg.Append(" and");1585 }1586 else if (idx < pausedOperations.Count - 1)1587 {1588 msg.Append(',');1589 }1590 }1591 msg.Append(pausedOperations.Count is 1 ? " is " : " are ");1592 msg.Append("paused on a dependency, but no other controlled operations are enabled.");1593 }1594 if (pausedOnResources.Count > 0)1595 {1596 for (int idx = 0; idx < pausedOnResources.Count; idx++)1597 {1598 msg.Append(string.Format(CultureInfo.InvariantCulture, " {0}", pausedOnResources[idx].Name));1599 if (idx == pausedOnResources.Count - 2)1600 {1601 msg.Append(" and");1602 }1603 else if (idx < pausedOnResources.Count - 1)1604 {1605 msg.Append(',');1606 }1607 }1608 msg.Append(pausedOnResources.Count is 1 ? " is " : " are ");1609 msg.Append("waiting to acquire a resource that is already acquired, ");1610 msg.Append("but no other controlled operations are enabled.");1611 }1612 if (pausedOnReceiveOperations.Count > 0)1613 {1614 for (int idx = 0; idx < pausedOnReceiveOperations.Count; idx++)1615 {1616 msg.Append(string.Format(CultureInfo.InvariantCulture, " {0}", pausedOnReceiveOperations[idx].Name));1617 if (idx == pausedOnReceiveOperations.Count - 2)1618 {1619 msg.Append(" and");1620 }1621 else if (idx < pausedOnReceiveOperations.Count - 1)1622 {1623 msg.Append(',');1624 }1625 }1626 msg.Append(pausedOnReceiveOperations.Count is 1 ? " is " : " are ");1627 msg.Append("waiting to receive an event, but no other controlled operations are enabled.");1628 }1629 if (this.IsUncontrolledConcurrencyDetected)1630 {1631 msg.Append(" Due to the presence of uncontrolled concurrency in the test, ");1632 msg.Append("Coyote cannot accurately determine if this is a real deadlock or not.");1633 if (!this.Configuration.ReportPotentialDeadlocksAsBugs)1634 {1635 this.Logger.WriteLine($"[coyote::test] {msg}");1636 this.Detach(ExecutionStatus.Deadlocked);1637 }1638 msg.Append(" If you believe that this is not a real deadlock, you can disable reporting ");1639 msg.Append("potential deadlocks as bugs by setting '--skip-potential-deadlocks' or ");1640 msg.Append("'Configuration.WithPotentialDeadlocksReportedAsBugs(false)'.");1641 }1642 this.NotifyAssertionFailure(msg.ToString());1643 }1644 /// <summary>1645 /// Periodically checks if the execution has deadlocked.1646 /// </summary>1647 private async Task CheckIfExecutionHasDeadlockedAsync()1648 {1649 var info = new SchedulingActivityInfo();1650 IO.Debug.WriteLine("[coyote::debug] Started periodic monitoring for potential deadlocks in runtime '{0}'.", this.Id);1651 while (true)1652 {1653 try1654 {1655 await Task.Delay(TimeSpan.FromMilliseconds(this.Configuration.DeadlockTimeout), this.CancellationSource.Token);1656 using (SynchronizedSection.Enter(this.RuntimeLock))1657 {1658 if (this.ExecutionStatus != ExecutionStatus.Running)1659 {1660 break;1661 }1662 if (info.OperationCount == this.OperationMap.Count &&1663 info.StepCount == this.Scheduler.StepCount)1664 {1665 string msg = "Potential deadlock detected. The periodic deadlock detection monitor was used, " +1666 "so Coyote cannot accurately determine if this is a real deadlock or not. If you believe " +1667 "that this is not a real deadlock, you can try increase the deadlock detection timeout " +1668 "by setting '--deadlock-timeout N' or 'Configuration.WithDeadlockTimeout(N)'.";1669 if (this.Configuration.ReportPotentialDeadlocksAsBugs)1670 {1671 msg += " Alternatively, you can disable reporting potential deadlocks as bugs by setting " +1672 "'--skip-potential-deadlocks' or 'Configuration.WithPotentialDeadlocksReportedAsBugs(false)'.";1673 this.NotifyAssertionFailure(msg);1674 }1675 else1676 {1677 this.Logger.WriteLine($"[coyote::test] {msg}");1678 this.Detach(ExecutionStatus.Deadlocked);1679 }1680 }1681 else1682 {1683 // Passed check, so continue with the next timeout period.1684 IO.Debug.WriteLine("[coyote::debug] Passed periodic check for potential deadlocks in runtime '{0}'.", this.Id);1685 info.OperationCount = this.OperationMap.Count;1686 info.StepCount = this.Scheduler.StepCount;1687 if (this.LastPostponedSchedulingPoint is SchedulingPointType.Pause ||1688 this.LastPostponedSchedulingPoint is SchedulingPointType.Complete)1689 {1690 // A scheduling point was postponed due to a potential deadlock, so try to check if it has been resolved.1691 this.ScheduleNextOperation(default, this.LastPostponedSchedulingPoint.Value, isSuppressible: false);1692 }1693 }1694 }1695 }1696 catch (TaskCanceledException)1697 {1698 break;1699 }1700 }1701 }1702 /// <summary>1703 /// Checks for liveness errors.1704 /// </summary>1705#if !DEBUG1706 [DebuggerHidden]1707#endif1708 internal void CheckLivenessErrors()1709 {1710 foreach (var monitor in this.TaskLivenessMonitors)1711 {1712 if (!monitor.IsSatisfied)1713 {1714 string msg = string.Format(CultureInfo.InvariantCulture,1715 "Found liveness bug at the end of program execution.\nThe stack trace is:\n{0}",1716 FormatSpecificationMonitorStackTrace(monitor.StackTrace));1717 this.NotifyAssertionFailure(msg);1718 }1719 }1720 // Checks if there is a specification monitor stuck in a hot state.1721 foreach (var monitor in this.SpecificationMonitors)1722 {1723 if (monitor.IsInHotState(out string stateName))1724 {1725 string msg = string.Format(CultureInfo.InvariantCulture,1726 "{0} detected liveness bug in hot state '{1}' at the end of program execution.",1727 monitor.GetType().FullName, stateName);1728 this.NotifyAssertionFailure(msg);1729 }1730 }1731 }1732 /// <summary>1733 /// Checks if a liveness monitor exceeded its threshold and, if yes, it reports an error.1734 /// </summary>1735 internal void CheckLivenessThresholdExceeded()1736 {1737 foreach (var monitor in this.TaskLivenessMonitors)1738 {1739 if (monitor.IsLivenessThresholdExceeded(this.Configuration.LivenessTemperatureThreshold))1740 {1741 string msg = string.Format(CultureInfo.InvariantCulture,1742 "Found potential liveness bug at the end of program execution.\nThe stack trace is:\n{0}",1743 FormatSpecificationMonitorStackTrace(monitor.StackTrace));1744 this.NotifyAssertionFailure(msg);1745 }1746 }1747 foreach (var monitor in this.SpecificationMonitors)1748 {1749 if (monitor.IsLivenessThresholdExceeded(this.Configuration.LivenessTemperatureThreshold))1750 {1751 string msg = $"{monitor.Name} detected potential liveness bug in hot state '{monitor.CurrentStateName}'.";1752 this.NotifyAssertionFailure(msg);1753 }1754 }1755 }1756 /// <summary>1757 /// Checks if the scheduling steps bound has been reached. If yes,1758 /// it stops the scheduler and kills all enabled machines.1759 /// </summary>1760 private void CheckIfSchedulingStepsBoundIsReached()1761 {1762 if (this.Scheduler.IsMaxStepsReached)1763 {1764 string message = $"Scheduling steps bound of {this.Scheduler.StepCount} reached.";1765 if (this.Configuration.ConsiderDepthBoundHitAsBug)1766 {1767 this.NotifyAssertionFailure(message);1768 }1769 else1770 {1771 IO.Debug.WriteLine($"[coyote::debug] {message}");1772 this.Detach(ExecutionStatus.BoundReached);1773 }1774 }1775 }1776 /// <summary>1777 /// Notify that an exception was not handled.1778 /// </summary>1779 internal void NotifyUnhandledException(Exception ex, string message)1780 {1781 using (SynchronizedSection.Enter(this.RuntimeLock))1782 {1783 if (this.ExecutionStatus != ExecutionStatus.Running)1784 {1785 return;1786 }1787 if (this.UnhandledException is null)1788 {1789 this.UnhandledException = ex;1790 }1791 this.NotifyAssertionFailure(message);1792 }1793 }1794 /// <summary>1795 /// Notify that an assertion has failed.1796 /// </summary>1797#if !DEBUG1798 [DebuggerHidden]1799#endif1800 internal void NotifyAssertionFailure(string text)1801 {1802 using (SynchronizedSection.Enter(this.RuntimeLock))1803 {1804 if (this.ExecutionStatus is ExecutionStatus.Running)1805 {1806 this.BugReport = text;1807 this.LogWriter.LogAssertionFailure($"[coyote::error] {text}");1808 this.RaiseOnFailureEvent(new AssertionFailureException(text));1809 if (this.Configuration.AttachDebugger)1810 {1811 Debugger.Break();1812 }1813 this.Detach(ExecutionStatus.BugFound);1814 }1815 }1816 }1817 /// <summary>1818 /// Notify that an uncontrolled invocation was detected.1819 /// </summary>1820 internal void NotifyUncontrolledInvocation(string methodName)1821 {1822 using (SynchronizedSection.Enter(this.RuntimeLock))1823 {1824 if (this.SchedulingPolicy != SchedulingPolicy.None)1825 {1826 this.UncontrolledInvocations.Add(methodName);1827 }1828 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)1829 {1830 string message = $"Invoking '{methodName}' is not intercepted and controlled during " +1831 "testing, so it can interfere with the ability to reproduce bug traces.";1832 this.TryHandleUncontrolledConcurrency(message, methodName);1833 }1834 }1835 }1836 /// <summary>1837 /// Notify that the currently executing thread is uncontrolled.1838 /// </summary>1839 private void NotifyUncontrolledCurrentThread()1840 {1841 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)1842 {1843 // TODO: figure out if there is a way to get more information about the creator of the1844 // uncontrolled thread to ease the user debugging experience.1845 string message = $"Executing thread '{Thread.CurrentThread.ManagedThreadId}' is not intercepted and " +1846 "controlled during testing, so it can interfere with the ability to reproduce bug traces.";1847 this.TryHandleUncontrolledConcurrency(message);1848 }1849 }1850 /// <summary>1851 /// Notify that an uncontrolled task is being waited.1852 /// </summary>1853 private void NotifyUncontrolledTaskWait(Task task)1854 {1855 using (SynchronizedSection.Enter(this.RuntimeLock))1856 {1857 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)1858 {1859 // TODO: figure out if there is a way to get more information about the creator of the1860 // uncontrolled task to ease the user debugging experience.1861 string message = $"Waiting task '{task.Id}' that is not intercepted and controlled during " +1862 "testing, so it can interfere with the ability to reproduce bug traces.";1863 if (this.TryHandleUncontrolledConcurrency(message) &&1864 this.UncontrolledTasks.Add(task))1865 {1866 this.TryPauseAndResolveUncontrolledTask(task);1867 }1868 }1869 }1870 }1871 /// <summary>1872 /// Notify that an uncontrolled task was returned.1873 /// </summary>1874 private void NotifyUncontrolledTaskReturned(Task task, string methodName)1875 {1876 using (SynchronizedSection.Enter(this.RuntimeLock))1877 {1878 if (this.SchedulingPolicy != SchedulingPolicy.None)1879 {1880 this.UncontrolledInvocations.Add(methodName);1881 }1882 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)1883 {1884 string message = $"Invoking '{methodName}' returned task '{task.Id}' that is not intercepted and " +1885 "controlled during testing, so it can interfere with the ability to reproduce bug traces.";1886 if (this.TryHandleUncontrolledConcurrency(message, methodName) &&1887 this.UncontrolledTasks.Add(task))1888 {1889 this.TryPauseAndResolveUncontrolledTask(task);1890 }1891 }1892 }1893 }1894 /// <summary>1895 /// Invoked when uncontrolled concurrency is detected. Based on the test configuration, it can try1896 /// handle the uncontrolled concurrency, else it terminates the current test iteration.1897 /// </summary>1898 private bool TryHandleUncontrolledConcurrency(string message, string methodName = default)1899 {1900 if (this.Configuration.IsPartiallyControlledConcurrencyAllowed)1901 {1902 IO.Debug.WriteLine($"[coyote::debug] {message}");1903 this.IsUncontrolledConcurrencyDetected = true;1904 return true;1905 }1906 else if (this.Configuration.IsSystematicFuzzingFallbackEnabled)1907 {1908 IO.Debug.WriteLine($"[coyote::debug] {message}");1909 this.IsUncontrolledConcurrencyDetected = true;1910 this.Detach(ExecutionStatus.ConcurrencyUncontrolled);1911 }1912 else1913 {1914 this.NotifyAssertionFailure(FormatUncontrolledConcurrencyExceptionMessage(message, methodName));1915 }1916 return false;1917 }1918 /// <summary>1919 /// Throws an <see cref="AssertionFailureException"/> exception containing the specified exception.1920 /// </summary>1921 internal void WrapAndThrowException(Exception exception, string s, params object[] args)1922 {1923 string msg = string.Format(CultureInfo.InvariantCulture, s, args);1924 string message = string.Format(CultureInfo.InvariantCulture,1925 "Exception '{0}' was thrown in {1}: {2}\n" +1926 "from location '{3}':\n" +1927 "The stack trace is:\n{4}",1928 exception.GetType(), msg, exception.Message, exception.Source, exception.StackTrace);1929 if (this.SchedulingPolicy is SchedulingPolicy.None)1930 {1931 throw new AssertionFailureException(message, exception);1932 }1933 this.NotifyUnhandledException(exception, message);1934 }1935 /// <summary>1936 /// Formats the message of the uncontrolled concurrency exception.1937 /// </summary>1938 private static string FormatUncontrolledConcurrencyExceptionMessage(string message, string methodName = default)1939 {1940 var mockMessage = methodName is null ? string.Empty : $" either replace or mock '{methodName}', or";1941 return $"{message} As a workaround, you can{mockMessage} use the '--no-repro' command line option " +1942 "(or the 'Configuration.WithNoBugTraceRepro()' method) to ignore this error by disabling bug " +1943 $"trace repro. Learn more at http://aka.ms/coyote-no-repro.\n{new StackTrace()}";1944 }1945 /// <summary>1946 /// Processes an unhandled exception in the specified controlled operation.1947 /// </summary>1948 internal void ProcessUnhandledExceptionInOperation(ControlledOperation op, Exception exception)1949 {1950 // Complete the failed operation. This is required so that the operation1951 // does not throw if it detaches.1952 op.Status = OperationStatus.Completed;1953 if (exception.GetBaseException() is ThreadInterruptedException)1954 {1955 // Ignore this exception, its thrown by the runtime.1956 IO.Debug.WriteLine("[coyote::debug] Controlled thread '{0}' executing operation '{1}' was interrupted.",1957 Thread.CurrentThread.ManagedThreadId, op.Name);1958 }1959 else1960 {1961 string message;1962 string trace = FormatExceptionStackTrace(exception);1963 if (op is ActorOperation actorOp)1964 {1965 message = string.Format(CultureInfo.InvariantCulture,1966 $"Unhandled exception in actor '{actorOp.Name}'. {trace}");1967 }1968 else1969 {1970 message = $"Unhandled exception. {trace}";1971 }1972 // Report the unhandled exception.1973 this.NotifyUnhandledException(exception, message);1974 }1975 }1976 /// <summary>1977 /// Formats the stack trace of the specified exception.1978 /// </summary>1979 private static string FormatExceptionStackTrace(Exception exception)1980 {1981 string[] lines = exception.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);1982 for (int i = 0; i < lines.Length; i++)1983 {1984 if (lines[i].StartsWith(" at Microsoft.Coyote.Rewriting", StringComparison.Ordinal))1985 {1986 lines[i] = string.Empty;1987 }1988 }1989 return string.Join(Environment.NewLine, lines.Where(line => !string.IsNullOrEmpty(line)));1990 }1991 /// <summary>1992 /// Formats the specified stack trace of a specification monitor.1993 /// </summary>1994 private static string FormatSpecificationMonitorStackTrace(StackTrace trace)1995 {1996 StringBuilder sb = new StringBuilder();1997 string[] lines = trace.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None);1998 foreach (var line in lines)1999 {2000 if ((line.Contains("at Microsoft.Coyote.Specifications") ||2001 line.Contains("at Microsoft.Coyote.Runtime")) &&2002 !line.Contains($"at {typeof(Specification).FullName}.{nameof(Specification.Monitor)}"))2003 {2004 continue;2005 }2006 sb.AppendLine(line);2007 }2008 return sb.ToString();2009 }2010 /// <summary>2011 /// Raises the <see cref="OnFailure"/> event with the specified <see cref="Exception"/>.2012 /// </summary>2013 internal void RaiseOnFailureEvent(Exception exception)2014 {2015 if (this.Configuration.AttachDebugger)2016 {2017 Debugger.Break();2018 this.Configuration.AttachDebugger = false;2019 }2020 this.OnFailure?.Invoke(exception);2021 }2022 /// <summary>2023 /// Populates the specified test report.2024 /// </summary>2025 internal void PopulateTestReport(ITestReport report)2026 {2027 using (SynchronizedSection.Enter(this.RuntimeLock))2028 {2029 bool isBugFound = this.ExecutionStatus is ExecutionStatus.BugFound;2030 report.SetSchedulingStatistics(isBugFound, this.BugReport, this.OperationMap.Count,2031 (int)this.MaxConcurrencyDegree, this.Scheduler.StepCount, this.Scheduler.IsMaxStepsReached,2032 this.Scheduler.IsScheduleFair);2033 if (isBugFound)2034 {2035 report.SetUnhandledException(this.UnhandledException);2036 }2037 report.SetUncontrolledInvocations(this.UncontrolledInvocations);2038 }2039 }2040 /// <summary>2041 /// Sets up the context of the executing controlled thread, allowing future retrieval2042 /// of runtime related data from the same thread, as well as across threads that share2043 /// the same asynchronous control flow.2044 /// </summary>2045 private void SetCurrentExecutionContext(ControlledOperation op)2046 {2047 ThreadLocalRuntime.Value = this;2048 AsyncLocalRuntime.Value = this;2049 this.SetControlledSynchronizationContext();2050 // Assign the specified controlled operation to the executing thread, and2051 // associate the operation group, if any, with the current context.2052 ExecutingOperation.Value = op;2053 OperationGroup.SetCurrent(op.Group);2054 }2055 /// <summary>2056 /// Removes any runtime related data from the context of the executing controlled thread.2057 /// </summary>2058 private static void CleanCurrentExecutionContext()2059 {2060 ExecutingOperation.Value = null;2061 AsyncLocalRuntime.Value = null;2062 ThreadLocalRuntime.Value = null;2063 OperationGroup.RemoveFromContext();2064 }2065 /// <summary>2066 /// Sets the synchronization context to the controlled synchronization context.2067 /// </summary>2068 private void SetControlledSynchronizationContext() =>2069 SynchronizationContext.SetSynchronizationContext(this.SyncContext);2070 /// <inheritdoc/>2071 public void RegisterLog(IRuntimeLog log) => this.LogWriter.RegisterLog(log);2072 /// <inheritdoc/>2073 public void RemoveLog(IRuntimeLog log) => this.LogWriter.RemoveLog(log);2074 /// <inheritdoc/>2075 public void Stop() => this.IsRunning = false;2076 /// <summary>2077 /// Detaches the scheduler and interrupts all controlled operations.2078 /// </summary>2079 /// <remarks>2080 /// It is assumed that this method runs in the scope of a <see cref="SynchronizedSection"/>.2081 /// </remarks>2082 private void Detach(ExecutionStatus status)2083 {2084 if (this.ExecutionStatus != ExecutionStatus.Running)2085 {2086 return;2087 }2088 try2089 {2090 if (status is ExecutionStatus.PathExplored)2091 {2092 this.Logger.WriteLine($"[coyote::test] Exploration finished in runtime '{this.Id}' [reached the end of the test method].");2093 }2094 else if (status is ExecutionStatus.BoundReached)2095 {2096 this.Logger.WriteLine($"[coyote::test] Exploration finished in runtime '{this.Id}' [reached the given bound].");2097 }2098 else if (status is ExecutionStatus.Deadlocked)2099 {2100 this.Logger.WriteLine($"[coyote::test] Exploration finished in runtime '{this.Id}' [detected a potential deadlock].");2101 }2102 else if (status is ExecutionStatus.ConcurrencyUncontrolled)2103 {2104 this.Logger.WriteLine($"[coyote::test] Exploration finished in runtime '{this.Id}' [detected uncontrolled concurrency].");2105 }2106 else if (status is ExecutionStatus.BugFound)2107 {2108 this.Logger.WriteLine($"[coyote::test] Exploration finished in runtime '{this.Id}' [found a bug using the '{0}' strategy].",2109 this.Configuration.SchedulingStrategy);2110 }2111 this.ExecutionStatus = status;2112 this.CancellationSource.Cancel();2113 // Complete any remaining operations at the end of the schedule.2114 foreach (var op in this.OperationMap.Values)2115 {2116 if (op.Status != OperationStatus.Completed)2117 {2118 op.Status = OperationStatus.Completed;2119 // Interrupt the thread executing this operation.2120 if (op == ExecutingOperation.Value)2121 {2122 throw new ThreadInterruptedException();2123 }2124 else if (this.ThreadPool.TryGetValue(op.Id, out Thread thread))2125 {2126 thread.Interrupt();2127 }2128 }2129 }2130 }2131 finally2132 {2133 // Check if the completion source is completed, else set its result.2134 if (!this.CompletionSource.Task.IsCompleted)2135 {2136 this.IsRunning = false;2137 this.CompletionSource.SetResult(true);2138 }2139 }2140 }2141 /// <summary>2142 /// Disposes runtime resources.2143 /// </summary>2144 private void Dispose(bool disposing)2145 {2146 if (disposing)2147 {2148 RuntimeProvider.Deregister(this.Id);2149 using (SynchronizedSection.Enter(this.RuntimeLock))2150 {2151 foreach (var op in this.OperationMap.Values)2152 {2153 op.Dispose();2154 }2155 foreach (var handler in this.PendingStartOperationMap.Values)2156 {2157 handler.Dispose();2158 }2159 this.ThreadPool.Clear();2160 this.OperationMap.Clear();2161 this.PendingStartOperationMap.Clear();2162 this.ControlledThreads.Clear();2163 this.ControlledTasks.Clear();2164 this.UncontrolledTasks.Clear();2165 this.UncontrolledInvocations.Clear();2166 this.SpecificationMonitors.Clear();2167 this.TaskLivenessMonitors.Clear();2168 this.DefaultActorExecutionContext.Dispose();2169 this.ControlledTaskScheduler.Dispose();2170 this.SyncContext.Dispose();2171 this.CancellationSource.Dispose();2172 }2173 if (this.SchedulingPolicy is SchedulingPolicy.Interleaving)2174 {2175 // Note: this makes it possible to run a Controlled unit test followed by a production2176 // unit test, whereas before that would throw "Uncontrolled Task" exceptions.2177 // This does not solve mixing unit test type in parallel.2178 Interlocked.Decrement(ref ExecutionControlledUseCount);2179 }2180 }2181 }2182 /// <summary>2183 /// Disposes runtime resources.2184 /// </summary>2185 public void Dispose()2186 {2187 this.Dispose(true);2188 GC.SuppressFinalize(this);2189 }2190 }2191}...
Monitor.cs
Source:Monitor.cs
...587 internal void Exit()588 {589 var op = this.Resource.Runtime.GetExecutingOperation();590 this.Resource.Runtime.Assert(this.LockCountMap.ContainsKey(op),591 "Cannot invoke Dispose without acquiring the lock.");592 this.LockCountMap[op]--;593 if (this.LockCountMap[op] is 0)594 {595 // Only release the lock if the invocation is not reentrant.596 this.LockCountMap.Remove(op);597 this.UnlockNextReady();598 this.Resource.Runtime.ScheduleNextOperation(op, SchedulingPointType.Release);599 }600 int useCount = SystemInterlocked.Decrement(ref this.UseCount);601 if (useCount is 0 && Cache[this.SyncObject].Value == this)602 {603 // It is safe to remove this instance from the cache.604 Cache.TryRemove(this.SyncObject, out _);605 }606 }607 /// <summary>608 /// Releases resources used by the synchronized block.609 /// </summary>610 protected void Dispose(bool disposing)611 {612 if (disposing)613 {614 this.Exit();615 }616 }617 /// <summary>618 /// Releases resources used by the synchronized block.619 /// </summary>620 public void Dispose()621 {622 this.Dispose(true);623 GC.SuppressFinalize(this);624 }625 /// <summary>626 /// The type of a pulse operation.627 /// </summary>628 private enum PulseOperation629 {630 /// <summary>631 /// Pulses the next waiting operation.632 /// </summary>633 Next,634 /// <summary>635 /// Pulses all waiting operations.636 /// </summary>...
ControlledOperation.cs
Source:ControlledOperation.cs
...122 {123 this.SyncEvent.Wait();124 this.SyncEvent.Reset();125 }126 catch (ObjectDisposedException)127 {128 // The handler was disposed, so we can ignore this exception.129 }130 }131 /// <summary>132 /// Signals this operation to resume its execution.133 /// </summary>134 internal void Signal() => this.SyncEvent.Set();135 /// <summary>136 /// Sets a callback that executes the next continuation of this operation.137 /// </summary>138 internal void SetContinuationCallback(Action callback) => this.Continuations.Enqueue(callback);139 /// <summary>140 /// Pauses this operation and sets a callback that returns true when the141 /// dependency causing the pause has been resolved.142 /// </summary>143 internal void PauseWithDependency(Func<bool> callback, bool isControlled)144 {145 this.Status = OperationStatus.Paused;146 this.Dependency = callback;147 this.IsDependencyUncontrolled = !isControlled;148 }149 /// <summary>150 /// Tries to enable this operation if its dependency has been resolved.151 /// </summary>152 internal bool TryEnable()153 {154 if (this.Status is OperationStatus.Paused && (this.Dependency?.Invoke() ?? true))155 {156 this.Dependency = null;157 this.IsDependencyUncontrolled = false;158 this.Status = OperationStatus.Enabled;159 }160 return this.Status is OperationStatus.Enabled;161 }162 /// <summary>163 /// Returns the hashed state of this operation for the specified policy.164 /// </summary>165 internal virtual int GetHashedState(SchedulingPolicy policy) => 0;166 /// <summary>167 /// Determines whether the specified object is equal to the current object.168 /// </summary>169 public override bool Equals(object obj)170 {171 if (obj is ControlledOperation op)172 {173 return this.Id == op.Id;174 }175 return false;176 }177 /// <summary>178 /// Returns the hash code for this instance.179 /// </summary>180 public override int GetHashCode() => this.Id.GetHashCode();181 /// <summary>182 /// Returns a string that represents the current operation id.183 /// </summary>184 public override string ToString() => this.Name;185 /// <summary>186 /// Indicates whether the specified <see cref="ControlledOperation"/> is equal187 /// to the current <see cref="ControlledOperation"/>.188 /// </summary>189 public bool Equals(ControlledOperation other) => this.Equals((object)other);190 /// <summary>191 /// Indicates whether the specified <see cref="ControlledOperation"/> is equal192 /// to the current <see cref="ControlledOperation"/>.193 /// </summary>194 bool IEquatable<ControlledOperation>.Equals(ControlledOperation other) => this.Equals(other);195 /// <summary>196 /// Releases any held resources.197 /// </summary>198 public void Dispose()199 {200 this.SyncEvent.Dispose();201 }202 }203}...
Dispose
Using AI Code Generation
1using Microsoft.Coyote;2using Microsoft.Coyote.Runtime;3using Microsoft.Coyote.Tasks;4using System;5using System.Threading.Tasks;6{7 {8 static void Main(string[] args)9 {10 ControlledOperation op = new ControlledOperation();11 Task task = Task.Run(() => { Console.WriteLine("Hello World!"); });12 op.Wait(task);13 op.Dispose();14 Console.ReadLine();15 }16 }17}18using Microsoft.Coyote;19using Microsoft.Coyote.Runtime;20using Microsoft.Coyote.Tasks;21using System;22using System.Threading.Tasks;23{24 {25 static void Main(string[] args)26 {27 ControlledOperation op = new ControlledOperation();28 Task task = Task.Run(() => { Console.WriteLine("Hello World!"); });29 op.Wait(task);30 op.Dispose();31 Console.ReadLine();32 }33 }34}35using Microsoft.Coyote;36using Microsoft.Coyote.Runtime;37using Microsoft.Coyote.Tasks;38using System;39using System.Threading.Tasks;40{41 {42 static void Main(string[] args)43 {44 ControlledOperation op = new ControlledOperation();45 Task task = Task.Run(() => { Console.WriteLine("Hello World!"); });46 op.Wait(task);47 op.Dispose();48 Console.ReadLine();49 }50 }51}52using Microsoft.Coyote;53using Microsoft.Coyote.Runtime;54using Microsoft.Coyote.Tasks;55using System;56using System.Threading.Tasks;57{58 {59 static void Main(string[] args)60 {61 ControlledOperation op = new ControlledOperation();62 Task task = Task.Run(() => { Console.WriteLine("Hello World!"); });63 op.Wait(task);64 op.Dispose();65 Console.ReadLine();66 }67 }68}
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!