Skip to content

Replacement for managed EnC module updates #29223

@tmat

Description

@tmat
// Each participating language service exports this interface. 
public interface IEditAndContinueManagedModuleUpdateProvider
{
    // Returns the kind of change made to the modules owned by the provider. 
    // The change is aggregated across all applicable modules: 
    // if one module has a rude edit and another one has valid change the resulting kind is
    // ManagedModuleUpdateStatus.Blocked.
    // If sourceFilePath is not null considers only modules whose compilation is affected by 
    // the specified source file, otherwise considers all modules owned by the provider.
    Task<ManagedModuleUpdateStatus> GetStatusAsync(string sourceFilePath, CancallationToken cancellationToken);

    // Returns an array of code changes (for each module owned by the provider) or 
    // empty array if there was no change or error. Errors are reported by the language service.
    Task<ManagedModuleUpdates> GetManagedModuleUpdatesAsync(CancallationToken cancellationToken);

   // Notifies the provider that changes to all modules have been successfully applied, 
   // except for modules listed in failures.
   void CommitUpdates(ImmutableArray<ManagedModuleUpdateFailure> failures);
}

public readonly struct ManagedModuleUpdateFailure
{
    public Guid ModuleId { get; } 
    public int ErrorCode { get; }
}

public readonly struct ManagedModuleUpdates
{   
    ManagedModuleUpdateStatus Status { get; }

    // Empty if Status != Ready.
    ImmutableArray<ManagedModuleUpdate> Updates { get; }
}

public enum ManagedModuleUpdateStatus
{  
   None, // No change made.
   Ready, // All changes are valid, can be applied.
   Blocked, // Some changes are invalid, can't continue.
}

public readonly struct ManagedModuleUpdate
{
    public Guid ModuleId { get; } 

    // applied to PE file:
    public ImmutableArray<byte> ILDelta { get; } 
    public ImmutableArray<byte> MetadataDelta { get; } 

    // applied to PDB
    public ImmutableArray<byte> PdbDelta { get; } 
    public ImmutableArray<SequencePointsUpdate> SequencePoints { get; } 

    public ImmutableArray<int> UpdatedMethods { get; } 
    public ImmutableArray<ActiveStatementUpdate> ActiveStatements { get; } 
    public ImmutableArray<ExceptionRegionUpdate> ExceptionRegions { get; }     // leave instructions remapping
}

public readonly struct ActiveStatementUpdate // corresponds to ENCPROG_ACTIVE_STATEMENT_REMAP
{
  public Guid ThreadId { get; }
  public int MethodToken { get; }
  public int MethodVersion { get; }
  public int ILOffset { get; }
  public TextSpan NewSpan  { get; }
}

public readonly struct ExceptionRegionUpdate // corresponds to ENCPROG_EXCEPTION_RANGE
{
   public int MethodToken { get; }
   public int MethodVersion { get; }
   public TextSpan NewSpan { get; }
   public int LineDelta { get; }
}

public readonly struct SequencePointsUpdate  // replacement for FILEUPDATE
{
   public string FileName { get; } // as stored in PDB
   public ImmutableArray<SourceLineUpdate> LineUpdates { get; }
}

public readonly struct SourceLineUpdate 
{
   public int OldLine { get; }
   public int NewLine { get; }
}

In CEncMgr::ApplyCodeChangesInternal replace

        if (Helpers::IsManagedENC(pCurrentProject))
        {
            if (HasManagedEditsReady(pCurrentProject))
            {
                projects[cModifiedProjects].m_project.Attach(pCurrentProject.Detach());
                cModifiedProjects++;
                continue;
            }
        }

with

        if (Helpers::IsManagedENC(pCurrentProject))
        {
            continue;
        }

and add after the the current project loop a call to managed code that does this:

  var providers = mef import all IEditAndContinueManagedModuleUpdateProvider;

  foreach (var provider in providers)
  {
     if (await provider.GetStatusAsync(cancellationToken) == ManagedModuleUpdateStatus.Blocked) 
     { 
        Return false;
     }
  }

Replace

   hr = RebuildManagedProjects(cModifiedProjects, projects.GetData(), &fHadCompilerError, &fENCModifiedModuleLoaded, &fENCNotSupported, result);

with a call to managed code that does:

  var providers = mef import all IEditAndContinueManagedModuleUpdateProvider;
  var allUpdates = new List<ImmutableArray<ManagedModuleUpdate>>();  

  foreach (var provider in providers)
  {
     var updates = await provider.GetManagedModuleUpdatesAsync(cancellationToken);
      
     if (updates.Any(u => u.Status == ManagedModuleUpdateStatus.Blocked)) 
     { 
        Return false;
     }

     allUpdates.Add(updates);
  }

  var failures = new List<ManagedModuleUpdateFailure>();
  foreach (var updates in allUpdates)
  {
     foreach (var update in updates)
     {
        var error = TryApplyUpdate(update);
        if (error != S_OK) 
        {
           failures.Add(new ManagedModuleUpdateFailure(update.ModuleId, error));
        } 
     }
  }
  
  var failuresImmutable = failures.ToImmutable();
  foreach (var provider in providers)
  {  
     provider.CommitUpdates(failuresImmutable);
  }

  if (failedModules.Any()) 
  {
     return false;
  }

Corresponding VSO bug: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/662811

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions