private void Destruct()

in rd-net/Lifetimes/Lifetimes/LifetimeDefinition.cs [516:585]


    private void Destruct()
    {
      var status = Status;
      if (Mode.IsAssertion) Assertion.Assert(status == LifetimeStatus.Terminating, "{0}: bad status for destructuring start", this);
      if (Mode.IsAssertion) Assertion.Assert(ourMutexSlice[myState] == false, "{0}: mutex must be released in this point", this);
      //no one can take mutex after this point

      var resources = myResources!;
      if (Mode.IsAssertion) Assertion.AssertNotNull(resources, "{0}: `resources` can't be null on destructuring stage", this);
      
      for (var i = myResCount - 1; i >= 0; i--)
      {
        try
        {
          switch (resources[i])
          {
            case Action a:
              a();
              break;

            case LifetimeDefinition ld:
              ld.Terminate();
              break;

            case IDisposable d:
              d.Dispose();
              break;
            
            case ITerminationHandler th:
              th.OnTermination(Lifetime);
              break;

            default:
              Log.Error("{0}: unknown type of termination resource: {1}", this, resources[i]);
              break;
          }
        }
        catch (Exception e)
        {
          Log.Error(e, $"{this}: exception on termination of resource[{i}]: ${resources[i]}");
        }
      }


      myResources = null;
      myResCount = 0;
      
      //In fact we shouldn't make it, because it should provide stable CancellationToken to finish enclosing tasks in Canceled state (not Faulted)
      //But to avoid memory leaks we must do it. So if you 1) run task with alive lifetime 2) terminate lifetime 3) in task invoke ThrowIfNotAlive() you can obtain `Faulted` state rather than `Canceled`. But it doesn't matter in `async-await` programming.     
      if (!ReferenceEquals(this, Terminated))
      {
        // we need to cancel myCts because a race condition is possible
        
        // Thread 1 MarkCancelingRecursively -> Exchange status Alive to Cancelling
        // Thread 2 Terminate -> (skip MarkCancelingRecursively because Status > Alive) -> Destruct -> myCts = Terminated.myCts
        // Thread 1 myTcs?.Cancel() -> the original cts will never be cancelled
        
        // so we have to cancel it here
        if (myCts is { } cts) 
          cts.Cancel();
        
        // synchronization is not needed here, because the creation of cts happens only under UnderMutexCookie(Alive)
        myCts = Terminated.myCts;
      }
      
      var statusIncrementedSuccessfully = IncrementStatusIfEqualsTo(LifetimeStatus.Terminating);
      myId = null;
      
      if (Mode.IsAssertion) Assertion.Assert(statusIncrementedSuccessfully, "{0}: bad status for destructuring finish", this);
    }