mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-06-13 22:48:42 +00:00
Deadlock tests
This commit is contained in:
@@ -56,25 +56,29 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
//public event PropertyModifiedEvent PropertyModified;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
uint instanceId;
|
||||
TypeDef typeDef;
|
||||
EpConnection connection;
|
||||
uint _instanceId;
|
||||
TypeDef _typeDef;
|
||||
EpConnection _connection;
|
||||
|
||||
|
||||
bool attached = false;
|
||||
bool destroyed = false;
|
||||
bool suspended = false;
|
||||
// Single explicit lifecycle state, replacing the former attached/destroyed/suspended booleans.
|
||||
Resource.ResourceStatus _status = Resource.ResourceStatus.Pending;
|
||||
|
||||
// Internal read-only views kept so the existing guard checks read naturally.
|
||||
//bool attached => status == Resource.ResourceStatus.Attached || status == Resource.ResourceStatus.Published;
|
||||
//bool destroyed => status == Resource.ResourceStatus.Destroyed;
|
||||
//bool suspended => status == Resource.ResourceStatus.Suspended;
|
||||
|
||||
//Structure properties = new Structure();
|
||||
|
||||
string link;
|
||||
ulong age;
|
||||
string _link;
|
||||
ulong _age;
|
||||
|
||||
protected object[] properties;
|
||||
internal List<EpResource> parents = new List<EpResource>();
|
||||
internal List<EpResource> children = new List<EpResource>();
|
||||
protected object[] _properties;
|
||||
//internal List<EpResource> parents = new List<EpResource>();
|
||||
//internal List<EpResource> children = new List<EpResource>();
|
||||
|
||||
EpResourceEvent[] events;
|
||||
EpResourceEvent[] _events;
|
||||
|
||||
|
||||
|
||||
@@ -83,7 +87,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
/// </summary>
|
||||
public EpConnection ResourceConnection
|
||||
{
|
||||
get { return connection; }
|
||||
get { return _connection; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -91,7 +95,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
/// </summary>
|
||||
public string ResourceLink
|
||||
{
|
||||
get { return link; }
|
||||
get { return _link; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -99,8 +103,8 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
/// </summary>
|
||||
public uint ResourceInstanceId
|
||||
{
|
||||
get { return instanceId; }
|
||||
internal set { instanceId = value; }
|
||||
get { return _instanceId; }
|
||||
internal set { _instanceId = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,9 +112,8 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
/// </summary>
|
||||
public void Destroy()
|
||||
{
|
||||
destroyed = true;
|
||||
attached = false;
|
||||
connection.SendDetachRequest(instanceId);
|
||||
_status = Resource.ResourceStatus.Destroyed;
|
||||
_connection.SendDetachRequest(_instanceId);
|
||||
OnDestroy?.Invoke(this);
|
||||
}
|
||||
|
||||
@@ -120,17 +123,69 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
|
||||
internal void Suspend()
|
||||
{
|
||||
suspended = true;
|
||||
attached = false;
|
||||
_status = Resource.ResourceStatus.Suspended;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the resource as published: attached and delivered to the application as part of a
|
||||
/// fully-attached object graph. A resource only transitions Attached -> Published.
|
||||
/// </summary>
|
||||
internal void Publish()
|
||||
{
|
||||
if (_status == Resource.ResourceStatus.Attached)
|
||||
_status = Resource.ResourceStatus.Published;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resource is attached when all its properties are received.
|
||||
/// The resource's current lifecycle state. Only <see cref="Resource.ResourceStatus.Published"/>
|
||||
/// guarantees the resource and its whole dependency graph are ready for application use.
|
||||
/// </summary>
|
||||
public bool ResourceAttached => attached;
|
||||
public Resource.ResourceStatus Status => _status;
|
||||
|
||||
public bool ResourceSuspended => suspended;
|
||||
/// <summary>
|
||||
/// Resource is attached when all its own properties are received (it may be Published too).
|
||||
/// </summary>
|
||||
//public bool ResourceAttached => attached;
|
||||
|
||||
//public bool ResourceSuspended => suspended;
|
||||
|
||||
/// <summary>True once the resource has been published to the application.</summary>
|
||||
//public bool ResourcePublished => status == Resource.ResourceStatus.Published;
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the distributed resources directly referenced by this resource's property values
|
||||
/// (including those nested inside arrays/lists/maps). Used to walk the dependency graph when
|
||||
/// publishing a fully-attached graph to the application.
|
||||
/// </summary>
|
||||
internal IEnumerable<EpResource> GetReferencedResources()
|
||||
{
|
||||
if (_properties == null)
|
||||
yield break;
|
||||
|
||||
foreach (var value in _properties)
|
||||
foreach (var resource in FlattenResources(value))
|
||||
yield return resource;
|
||||
}
|
||||
|
||||
static IEnumerable<EpResource> FlattenResources(object value)
|
||||
{
|
||||
if (value is EpResource resource)
|
||||
{
|
||||
yield return resource;
|
||||
}
|
||||
else if (value is System.Collections.IDictionary dictionary)
|
||||
{
|
||||
foreach (var item in dictionary.Values)
|
||||
foreach (var r in FlattenResources(item))
|
||||
yield return r;
|
||||
}
|
||||
else if (value is System.Collections.IEnumerable sequence && !(value is string))
|
||||
{
|
||||
foreach (var item in sequence)
|
||||
foreach (var r in FlattenResources(item))
|
||||
yield return r;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// public DistributedResourceStack Stack
|
||||
@@ -146,40 +201,65 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
/// <param name="age">Resource age.</param>
|
||||
public EpResource(EpConnection connection, uint instanceId, ulong age, string link)
|
||||
{
|
||||
this.link = link;
|
||||
this.connection = connection;
|
||||
this.instanceId = instanceId;
|
||||
this.age = age;
|
||||
this._link = link;
|
||||
this._connection = connection;
|
||||
this._instanceId = instanceId;
|
||||
this._age = age;
|
||||
}
|
||||
|
||||
internal bool _Attach(PropertyValue[] properties)
|
||||
{
|
||||
if (attached)
|
||||
if (_status == ResourceStatus.Attached)
|
||||
return false;
|
||||
else
|
||||
|
||||
_properties = new object[properties.Length];
|
||||
|
||||
_events = new EpResourceEvent[Instance.Definition.Events.Length];
|
||||
|
||||
for (byte i = 0; i < properties.Length; i++)
|
||||
{
|
||||
suspended = false;
|
||||
|
||||
this.properties = new object[properties.Length];
|
||||
|
||||
this.events = new EpResourceEvent[Instance.Definition.Events.Length];
|
||||
|
||||
for (byte i = 0; i < properties.Length; i++)
|
||||
{
|
||||
Instance.SetAge(i, properties[i].Age);
|
||||
Instance.SetModificationDate(i, properties[i].Date);
|
||||
this.properties[i] = properties[i].Value;
|
||||
}
|
||||
|
||||
// trigger holded events/property updates.
|
||||
//foreach (var r in afterAttachmentTriggers)
|
||||
// r.Key.Trigger(r.Value);
|
||||
|
||||
//afterAttachmentTriggers.Clear();
|
||||
|
||||
attached = true;
|
||||
|
||||
Instance.SetAge(i, properties[i].Age);
|
||||
Instance.SetModificationDate(i, properties[i].Date);
|
||||
this._properties[i] = properties[i].Value;
|
||||
}
|
||||
|
||||
// trigger holded events/property updates.
|
||||
//foreach (var r in afterAttachmentTriggers)
|
||||
// r.Key.Trigger(r.Value);
|
||||
|
||||
//afterAttachmentTriggers.Clear();
|
||||
|
||||
_status = Resource.ResourceStatus.Attached;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-attaches a previously attached (then suspended) resource after reconnection by merging
|
||||
/// only the properties that changed while disconnected. The peer returns just the delta — the
|
||||
/// properties whose age is newer than the age this side last knew — so unchanged properties
|
||||
/// keep their existing value/age/date. Returns false if the resource was never attached (no
|
||||
/// prior state to merge into), in which case the caller should perform a full attach.
|
||||
/// </summary>
|
||||
/// <param name="delta">Modified properties keyed by their property index.</param>
|
||||
internal bool _Reattach(Map<byte, PropertyValue> delta)
|
||||
{
|
||||
if (_properties == null || _events == null)
|
||||
return false; // no prior state — caller should perform a full attach instead.
|
||||
|
||||
foreach (var kv in delta)
|
||||
{
|
||||
var index = kv.Key;
|
||||
if (index >= _properties.Length)
|
||||
continue;
|
||||
|
||||
Instance.SetAge(index, kv.Value.Age);
|
||||
Instance.SetModificationDate(index, kv.Value.Date);
|
||||
_properties[index] = kv.Value.Value;
|
||||
}
|
||||
|
||||
_status = Resource.ResourceStatus.Attached;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -187,16 +267,17 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
protected internal virtual void _EmitEventByIndex(byte index, object args)
|
||||
{
|
||||
var et = Instance.Definition.GetEventDefByIndex(index);
|
||||
events[index]?.Invoke(this, args);
|
||||
_events[index]?.Invoke(this, args);
|
||||
Instance.EmitResourceEvent(et, args);
|
||||
}
|
||||
|
||||
public AsyncReply _Invoke(byte index, object args)
|
||||
{
|
||||
if (destroyed)
|
||||
|
||||
if (_status == ResourceStatus.Destroyed)
|
||||
throw new Exception("Trying to access a destroyed object.");
|
||||
|
||||
if (suspended)
|
||||
if (_status == ResourceStatus.Suspended)
|
||||
throw new Exception("Trying to access a suspended object.");
|
||||
|
||||
if (index >= Instance.Definition.Functions.Length)
|
||||
@@ -208,9 +289,9 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
throw new Exception("Function definition not found.");
|
||||
|
||||
if (ft.IsStatic)
|
||||
return connection.StaticCall(Instance.Definition.Id, index, args);
|
||||
return _connection.StaticCall(Instance.Definition.Id, index, args);
|
||||
else
|
||||
return connection.SendInvoke(instanceId, index, args);
|
||||
return _connection.SendInvoke(_instanceId, index, args);
|
||||
}
|
||||
|
||||
public AsyncReply Subscribe(EventDef et)
|
||||
@@ -229,7 +310,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
return rt;
|
||||
}
|
||||
|
||||
return connection.SendSubscribeRequest(instanceId, et.Index);
|
||||
return _connection.SendSubscribeRequest(_instanceId, et.Index);
|
||||
}
|
||||
|
||||
public AsyncReply Subscribe(string eventName)
|
||||
@@ -244,7 +325,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
{
|
||||
if (et == null)
|
||||
{
|
||||
var rt = new AsyncReply();
|
||||
var rt = new AsyncReply();
|
||||
rt.TriggerError(new AsyncException(ErrorType.Management, (ushort)ExceptionCode.MethodNotFound, ""));
|
||||
return rt;
|
||||
}
|
||||
@@ -256,7 +337,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
return rt;
|
||||
}
|
||||
|
||||
return connection.SendUnsubscribeRequest(instanceId, et.Index);
|
||||
return _connection.SendUnsubscribeRequest(_instanceId, et.Index);
|
||||
}
|
||||
|
||||
public AsyncReply Unsubscribe(string eventName)
|
||||
@@ -269,61 +350,63 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
|
||||
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
|
||||
{
|
||||
if (destroyed)
|
||||
if (_status == ResourceStatus.Destroyed)
|
||||
throw new Exception("Trying to access a destroyed object.");
|
||||
|
||||
if (suspended)
|
||||
if (_status == ResourceStatus.Suspended)
|
||||
throw new Exception("Trying to access a suspended object.");
|
||||
|
||||
var ft = Instance.Definition.GetFunctionDefByName(binder.Name);
|
||||
|
||||
var reply = new AsyncReply<object>();
|
||||
|
||||
if (attached && ft != null)
|
||||
{
|
||||
|
||||
if (args.Length == 1)
|
||||
{
|
||||
// Detect anonymous types
|
||||
var type = args[0].GetType();
|
||||
|
||||
|
||||
if (Codec.IsAnonymous(type))
|
||||
{
|
||||
var indexedArgs = new Map<byte, object>();
|
||||
|
||||
var pis = type.GetProperties();
|
||||
|
||||
for (byte i = 0; i < ft.Arguments.Length; i++)
|
||||
{
|
||||
var pi = pis.FirstOrDefault(x => x.Name == ft.Arguments[i].Name);
|
||||
if (pi != null)
|
||||
indexedArgs.Add(i, pi.GetValue(args[0]));
|
||||
}
|
||||
|
||||
result = _Invoke(ft.Index, indexedArgs);
|
||||
}
|
||||
else if (args[0] is object[] || args[0] is Map<byte, object>)
|
||||
{
|
||||
result = _Invoke(ft.Index, new object[] { args });
|
||||
}
|
||||
else
|
||||
{
|
||||
result = _Invoke(ft.Index, args);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
result = _Invoke(ft.Index, args);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (_status != ResourceStatus.Attached)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var ft = Instance.Definition.GetFunctionDefByName(binder.Name)
|
||||
?? throw new Exception($"{binder.Name} does not exist");
|
||||
|
||||
var reply = new AsyncReply<object>();
|
||||
|
||||
|
||||
if (args.Length == 1)
|
||||
{
|
||||
// Detect anonymous types
|
||||
var type = args[0].GetType();
|
||||
|
||||
|
||||
if (Codec.IsAnonymous(type))
|
||||
{
|
||||
var indexedArgs = new Map<byte, object>();
|
||||
|
||||
var pis = type.GetProperties();
|
||||
|
||||
for (byte i = 0; i < ft.Arguments.Length; i++)
|
||||
{
|
||||
var pi = pis.FirstOrDefault(x => x.Name == ft.Arguments[i].Name);
|
||||
if (pi != null)
|
||||
indexedArgs.Add(i, pi.GetValue(args[0]));
|
||||
}
|
||||
|
||||
result = _Invoke(ft.Index, indexedArgs);
|
||||
}
|
||||
else if (args[0] is object[] || args[0] is Map<byte, object>)
|
||||
{
|
||||
result = _Invoke(ft.Index, new object[] { args });
|
||||
}
|
||||
else
|
||||
{
|
||||
result = _Invoke(ft.Index, args);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
result = _Invoke(ft.Index, args);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
@@ -337,34 +420,34 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
|
||||
public bool TryGetPropertyValue(byte index, out object value)
|
||||
{
|
||||
if (index >= properties.Length)
|
||||
if (index >= _properties.Length)
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = properties[index];
|
||||
value = _properties[index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object result)
|
||||
{
|
||||
if (destroyed)
|
||||
if (_status == ResourceStatus.Destroyed)
|
||||
throw new Exception("Trying to access a destroyed object.");
|
||||
|
||||
|
||||
result = null;
|
||||
|
||||
if (!attached)
|
||||
if (_status != ResourceStatus.Attached)
|
||||
return false;
|
||||
|
||||
var pt = Instance.Definition.GetPropertyDefByName(binder.Name);
|
||||
|
||||
if (pt != null)
|
||||
{
|
||||
result = properties[pt.Index];
|
||||
result = _properties[pt.Index];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -373,7 +456,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
if (et == null)
|
||||
return false;
|
||||
|
||||
result = events[et.Index];
|
||||
result = _events[et.Index];
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -383,7 +466,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
internal void _UpdatePropertyByIndex(byte index, object value)
|
||||
{
|
||||
var pt = Instance.Definition.GetPropertyDefByIndex(index);
|
||||
properties[index] = value;
|
||||
_properties[index] = value;
|
||||
Instance.EmitModification(pt, value);
|
||||
}
|
||||
|
||||
@@ -409,13 +492,13 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
|
||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
||||
{
|
||||
if (destroyed)
|
||||
if (_status == ResourceStatus.Destroyed)
|
||||
throw new Exception("Trying to access a destroyed object.");
|
||||
|
||||
if (suspended)
|
||||
if (_status == ResourceStatus.Suspended)
|
||||
throw new Exception("Trying to access a suspended object.");
|
||||
|
||||
if (!attached)
|
||||
if (_status != ResourceStatus.Attached)
|
||||
return false;
|
||||
|
||||
var pt = Instance.Definition.GetPropertyDefByName(binder.Name);
|
||||
@@ -431,7 +514,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
if (et == null)
|
||||
return false;
|
||||
|
||||
events[et.Index] = (EpResourceEvent)value;
|
||||
_events[et.Index] = (EpResourceEvent)value;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -452,11 +535,11 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
{
|
||||
get
|
||||
{
|
||||
return typeDef;
|
||||
return _typeDef;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
typeDef = value;
|
||||
_typeDef = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,10 +577,10 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
|
||||
public PropertyValue[] SerializeResource()
|
||||
{
|
||||
var props = new PropertyValue[properties.Length];
|
||||
var props = new PropertyValue[_properties.Length];
|
||||
|
||||
for (byte i = 0; i < properties.Length; i++)
|
||||
props[i] = new PropertyValue(properties[i],
|
||||
for (byte i = 0; i < _properties.Length; i++)
|
||||
props[i] = new PropertyValue(_properties[i],
|
||||
Instance.GetAge(i),
|
||||
Instance.GetModificationDate(i));
|
||||
|
||||
@@ -508,9 +591,9 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
{
|
||||
var rt = new Map<byte, PropertyValue>();
|
||||
|
||||
for (byte i = 0; i < properties.Length; i++)
|
||||
for (byte i = 0; i < _properties.Length; i++)
|
||||
if (Instance.GetAge(i) > age)
|
||||
rt.Add(i, new PropertyValue(properties[i],
|
||||
rt.Add(i, new PropertyValue(_properties[i],
|
||||
Instance.GetAge(i),
|
||||
Instance.GetModificationDate(i)));
|
||||
|
||||
@@ -523,33 +606,33 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
|
||||
public object GetResourceProperty(byte index)
|
||||
{
|
||||
if (index >= properties.Length)
|
||||
if (index >= _properties.Length)
|
||||
return null;
|
||||
return properties[index];
|
||||
return _properties[index];
|
||||
}
|
||||
|
||||
public AsyncReply SetResourcePropertyAsync(byte index, object value)
|
||||
{
|
||||
if (destroyed)
|
||||
if (_status == ResourceStatus.Destroyed)
|
||||
throw new Exception("Trying to access a destroyed object.");
|
||||
|
||||
if (suspended)
|
||||
if (_status == ResourceStatus.Suspended)
|
||||
throw new Exception("Trying to access a suspended object.");
|
||||
|
||||
if (!attached)
|
||||
if (_status != ResourceStatus.Attached)
|
||||
throw new Exception("Resource is not attached.");
|
||||
|
||||
if (index >= properties.Length)
|
||||
if (index >= _properties.Length)
|
||||
throw new Exception("Property index not found."); ;
|
||||
|
||||
var reply = new AsyncReply<object>();
|
||||
|
||||
connection.SendSetProperty(instanceId, index, value)
|
||||
_connection.SendSetProperty(_instanceId, index, value)
|
||||
.Then((res) =>
|
||||
{
|
||||
// not really needed, server will always send property modified,
|
||||
// this only happens if the programmer forgot to emit in property setter
|
||||
properties[index] = value;
|
||||
_properties[index] = value;
|
||||
reply.Trigger(null);
|
||||
});
|
||||
|
||||
@@ -560,7 +643,7 @@ public class EpResource : DynamicObject, IResource, INotifyPropertyChanged, IDyn
|
||||
public void SetResourceProperty(byte index, object value)
|
||||
{
|
||||
// Don't set the same current value
|
||||
if (properties[index] == value)
|
||||
if (_properties[index] == value)
|
||||
return;
|
||||
|
||||
SetResourcePropertyAsync(index, value).Wait();
|
||||
|
||||
Reference in New Issue
Block a user