Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/site/src/content/docs/implementation/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Boxed value-type delegates initialize the existing box. Statically closed value-

Grain-reference construction resolves the generated proxy from the registered type manifest and invokes its `(GrainReferenceShared, IdSpan)` constructor. The activator caches the constructor delegate and shares the runtime, interface version, invocation options, and serialization services across references for the same grain type and interface. Each reference retains its own grain key. JIT runtimes use an emitted constructor delegate; NativeAOT uses a cached reflection constructor invoker. Generated `AddInterfaceProxy` registrations preserve the public proxy constructor during trimming.

Generated proxies retain concrete generated and built-in copier types in readonly fields, allowing direct copier calls. The generated-code helper delegates dependency resolution to the codec provider, which caches registered serializer, codec, copier, activator, and converter implementations by their closed implementation type. Generated dependencies and provider lookups reuse completed implementations within the same provider, including closed generic implementations. Other dependencies retain their configured dependency-injection lifetimes. Interface-typed dependencies follow their configured registrations. A shared copy context preserves cycles and repeated references across the arguments of one call.

The provider retains completed implementations and their dependency graphs for its lifetime, using its existing service cache. Concurrent cold resolutions can construct multiple implementations and converge on the published instance; closed service factories use the provider's coordinated construction graph. Warm lookups reuse completed instances without allocating construction state.

For each grain interface method, generated code captures arguments in an invokable object. The generated proxy submits that object through its proxy base. On the target, generated dispatch metadata invokes the concrete implementation and encodes the response.

The request object is serializable like any other Orleans value. Stable method and interface metadata allow caller and target assemblies to evolve independently within the supported versioning rules. Outgoing and incoming call filters wrap the generated invocation; they do not replace serialization or dispatch.
Expand Down Expand Up @@ -145,6 +149,10 @@ The method identity uses an explicit <xref:Orleans.IdAttribute> value, an <xref:

The message also carries the grain interface type and version used by version selection. Type identity resolves the serialized request body; interface/version metadata selects compatible dispatch. These are related compatibility boundaries with separate responsibilities.

The interface-type resolver caches successful identities for its lifetime, keyed by the CLR interface type. Each closed generic interface has its own cache entry with its exact type arguments. Configured identity providers retain their precedence over naming conventions and define stable identities for the resolver's lifetime.

Each entry retains its CLR interface type and formatted identity until the resolver is released. Cache size follows the distinct interfaces and closed generic combinations used by the application.

### Source and referenced assembly metadata

Syntax providers create stable models for source-declared serializable types and proxy interfaces. A separate compilation-dependent stage scans source and referenced assembly metadata for application parts, aliases, compound aliases, serializer registrations, proxy interfaces, implementations, and assembly-level invokable mappings.
Expand Down
11 changes: 11 additions & 0 deletions src/Orleans.Core.Abstractions/IDs/GrainInterfaceType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ public interface IGrainInterfaceTypeProvider
/// <returns>
/// <see langword="true"/> if a <see cref="GrainInterfaceType"/> corresponding to the provided type was found, otherwise <see langword="false"/>.
/// </returns>
/// <remarks>
/// Implementations must return a stable success result and identifier for each CLR interface type
/// for the lifetime of the resolver using this provider. The resolver caches resolved identities,
/// including closed generic interface types. Implementations must support concurrent calls;
/// concurrent cache misses can invoke this method more than once for the same type.
/// </remarks>
bool TryGetGrainInterfaceType(Type type, out GrainInterfaceType grainInterfaceType);
}

Expand Down Expand Up @@ -145,6 +151,11 @@ public interface IGrainInterfaceTypeProviderAttribute
/// <returns>
/// The <see cref="GrainInterfaceType"/> corresponding to the provided type.
/// </returns>
/// <remarks>
/// The identifier must remain stable for each CLR interface type for the lifetime of the resolver
/// using this attribute. Implementations must support concurrent calls, including repeated calls
/// for the same type during concurrent identity resolution.
/// </remarks>
GrainInterfaceType GetGrainInterfaceType(IServiceProvider services, Type type);
}

Expand Down
13 changes: 12 additions & 1 deletion src/Orleans.Core/Manifest/GrainInterfaceTypeResolver.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Orleans.Runtime;
Expand All @@ -13,6 +14,7 @@ public class GrainInterfaceTypeResolver
{
private readonly IGrainInterfaceTypeProvider[] _providers;
private readonly TypeConverter _typeConverter;
private readonly ConcurrentDictionary<Type, GrainInterfaceType> _interfaceTypes = new();

/// <summary>
/// Initializes a new instance of the <see cref="GrainInterfaceTypeResolver"/> class.
Expand All @@ -32,11 +34,20 @@ public GrainInterfaceTypeResolver(
}

/// <summary>
/// Returns the <see cref="GrainInterfaceType"/> for the provided interface.
/// Returns the cached <see cref="GrainInterfaceType"/> for the provided interface.
/// </summary>
/// <remarks>
/// Identities are cached per resolver and CLR interface type, including closed generic arguments.
/// Configured providers define stable identities for the lifetime of the resolver.
Comment thread
ReubenBond marked this conversation as resolved.
Resolved
/// </remarks>
/// <param name="type">The grain interface.</param>
/// <returns>The <see cref="GrainInterfaceType"/> for the provided interface.</returns>
public GrainInterfaceType GetGrainInterfaceType(Type type)
{
return _interfaceTypes.GetOrAdd(type, static (interfaceType, resolver) => resolver.ResolveGrainInterfaceType(interfaceType), this);
}

private GrainInterfaceType ResolveGrainInterfaceType(Type type)
{
if (!type.IsInterface)
{
Expand Down
Loading
Loading