mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-04-29 06:48:41 +00:00
Tests
This commit is contained in:
@@ -58,22 +58,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ConcurrentAttach", "Concurr
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ResourceCount", "ResourceCount", "{058F6BE3-A684-45F9-B61A-25839C64F503}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ResourceCount", "ResourceCount", "{058F6BE3-A684-45F9-B61A-25839C64F503}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{17796DCC-760D-4AD7-BCA9-EFE4801B9044}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{FFF7D07F-BA9F-4129-B7AD-99861E11F05E}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{DA2EC9AF-E2D9-4B8D-8EC3-CC65CFD3B974}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{DA2EC9AF-E2D9-4B8D-8EC3-CC65CFD3B974}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{413A4292-C2B3-4096-94CF-D6F607C20939}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{413A4292-C2B3-4096-94CF-D6F607C20939}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ConcurrentAttach.Client", "Tests\Distribution\ConcurrentAttach\Client\Esiur.Tests.ConcurrentAttach.Client.csproj", "{CD889154-4EA5-61D3-9FF4-E15F7B3D3573}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ConcurrentAttach.Server", "Tests\Distribution\ConcurrentAttach\Server\Esiur.Tests.ConcurrentAttach.Server.csproj", "{9A468603-1310-7434-3A2B-4528DA8221C6}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ResourceCount.Client", "Tests\Distribution\ResourceCount\Client\Esiur.Tests.ResourceCount.Client.csproj", "{69A075E7-D924-59C6-0BF2-17A09201DDF3}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ResourceCount.Client", "Tests\Distribution\ResourceCount\Client\Esiur.Tests.ResourceCount.Client.csproj", "{69A075E7-D924-59C6-0BF2-17A09201DDF3}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ResourceCount.Server", "Tests\Distribution\ResourceCount\Server\Esiur.Tests.ResourceCount.Server.csproj", "{D1DF309F-40DE-9C0E-A78B-2648544B77D2}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ResourceCount.Server", "Tests\Distribution\ResourceCount\Server\Esiur.Tests.ResourceCount.Server.csproj", "{D1DF309F-40DE-9C0E-A78B-2648544B77D2}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.ConcurrentAttach", "Tests\Distribution\ConcurrentAttach\Esiur.Tests.ConcurrentAttach.csproj", "{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -132,14 +126,6 @@ Global
|
|||||||
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D8340DC7-5D27-2A71-74CC-634493847FF0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{CD889154-4EA5-61D3-9FF4-E15F7B3D3573}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{CD889154-4EA5-61D3-9FF4-E15F7B3D3573}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{CD889154-4EA5-61D3-9FF4-E15F7B3D3573}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{CD889154-4EA5-61D3-9FF4-E15F7B3D3573}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{9A468603-1310-7434-3A2B-4528DA8221C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{9A468603-1310-7434-3A2B-4528DA8221C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{9A468603-1310-7434-3A2B-4528DA8221C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{9A468603-1310-7434-3A2B-4528DA8221C6}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{69A075E7-D924-59C6-0BF2-17A09201DDF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -148,6 +134,10 @@ Global
|
|||||||
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D1DF309F-40DE-9C0E-A78B-2648544B77D2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -175,14 +165,11 @@ Global
|
|||||||
{D8340DC7-5D27-2A71-74CC-634493847FF0} = {8AC1C925-068F-4E78-ADE4-4DA3CF996662}
|
{D8340DC7-5D27-2A71-74CC-634493847FF0} = {8AC1C925-068F-4E78-ADE4-4DA3CF996662}
|
||||||
{336B5CE1-95DA-4FDD-A876-0919E3C446CA} = {94C8CFDB-C7C6-40DF-A596-647FEEA3C917}
|
{336B5CE1-95DA-4FDD-A876-0919E3C446CA} = {94C8CFDB-C7C6-40DF-A596-647FEEA3C917}
|
||||||
{058F6BE3-A684-45F9-B61A-25839C64F503} = {94C8CFDB-C7C6-40DF-A596-647FEEA3C917}
|
{058F6BE3-A684-45F9-B61A-25839C64F503} = {94C8CFDB-C7C6-40DF-A596-647FEEA3C917}
|
||||||
{17796DCC-760D-4AD7-BCA9-EFE4801B9044} = {336B5CE1-95DA-4FDD-A876-0919E3C446CA}
|
|
||||||
{FFF7D07F-BA9F-4129-B7AD-99861E11F05E} = {336B5CE1-95DA-4FDD-A876-0919E3C446CA}
|
|
||||||
{DA2EC9AF-E2D9-4B8D-8EC3-CC65CFD3B974} = {058F6BE3-A684-45F9-B61A-25839C64F503}
|
{DA2EC9AF-E2D9-4B8D-8EC3-CC65CFD3B974} = {058F6BE3-A684-45F9-B61A-25839C64F503}
|
||||||
{413A4292-C2B3-4096-94CF-D6F607C20939} = {058F6BE3-A684-45F9-B61A-25839C64F503}
|
{413A4292-C2B3-4096-94CF-D6F607C20939} = {058F6BE3-A684-45F9-B61A-25839C64F503}
|
||||||
{CD889154-4EA5-61D3-9FF4-E15F7B3D3573} = {FFF7D07F-BA9F-4129-B7AD-99861E11F05E}
|
|
||||||
{9A468603-1310-7434-3A2B-4528DA8221C6} = {17796DCC-760D-4AD7-BCA9-EFE4801B9044}
|
|
||||||
{69A075E7-D924-59C6-0BF2-17A09201DDF3} = {413A4292-C2B3-4096-94CF-D6F607C20939}
|
{69A075E7-D924-59C6-0BF2-17A09201DDF3} = {413A4292-C2B3-4096-94CF-D6F607C20939}
|
||||||
{D1DF309F-40DE-9C0E-A78B-2648544B77D2} = {DA2EC9AF-E2D9-4B8D-8EC3-CC65CFD3B974}
|
{D1DF309F-40DE-9C0E-A78B-2648544B77D2} = {DA2EC9AF-E2D9-4B8D-8EC3-CC65CFD3B974}
|
||||||
|
{7D88CAF1-1887-A011-BA72-F38C87C1A7D9} = {336B5CE1-95DA-4FDD-A876-0919E3C446CA}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {C584421D-5EC0-4821-B7D8-2633D8D405F2}
|
SolutionGuid = {C584421D-5EC0-4821-B7D8-2633D8D405F2}
|
||||||
|
|||||||
@@ -267,15 +267,18 @@ namespace Esiur.Data
|
|||||||
// case TRUIdentifier. }
|
// case TRUIdentifier. }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
private static Dictionary<Type, Tru> _cache = new Dictionary<Type, Tru>();
|
private static Dictionary<Type, Tru> cache = new Dictionary<Type, Tru>();
|
||||||
|
private static object cacheLook = new object();
|
||||||
|
|
||||||
public static Tru? FromType(Type type)
|
public static Tru? FromType(Type type)
|
||||||
{
|
{
|
||||||
if (type == null)
|
if (type == null)
|
||||||
return new Tru(TruIdentifier.Void, true);
|
return new Tru(TruIdentifier.Void, true);
|
||||||
|
|
||||||
if (_cache.ContainsKey(type))
|
lock (cacheLook)
|
||||||
return _cache[type];
|
{
|
||||||
|
if (cache.ContainsKey(type))
|
||||||
|
return cache[type];
|
||||||
|
|
||||||
var nullable = false;
|
var nullable = false;
|
||||||
|
|
||||||
@@ -496,7 +499,7 @@ namespace Esiur.Data
|
|||||||
|
|
||||||
if (tru != null)
|
if (tru != null)
|
||||||
{
|
{
|
||||||
_cache.Add(type, tru);
|
cache.Add(type, tru);
|
||||||
return tru;
|
return tru;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +526,7 @@ namespace Esiur.Data
|
|||||||
_ when type == typeof(ResourceLink) => new Tru(TruIdentifier.Resource, nullable),
|
_ when type == typeof(ResourceLink) => new Tru(TruIdentifier.Resource, nullable),
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tru(TruIdentifier identifier, bool nullable, Uuid? uuid = null, Tru[]? subTypes = null)
|
public Tru(TruIdentifier identifier, bool nullable, Uuid? uuid = null, Tru[]? subTypes = null)
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ namespace Esiur.Net.Packets
|
|||||||
Stream = 0x2,
|
Stream = 0x2,
|
||||||
|
|
||||||
// Error
|
// Error
|
||||||
PermissionError = 0x81,
|
PermissionError = 0x4,
|
||||||
ExecutionError = 0x82,
|
ExecutionError = 0x5,
|
||||||
|
|
||||||
// Partial
|
// Partial
|
||||||
Progress = 0x10,
|
Progress = 0x8,
|
||||||
Chunk = 0x11,
|
Chunk = 0x9,
|
||||||
Warning = 0x12
|
Warning = 0xA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,9 +57,11 @@ partial class EpConnection
|
|||||||
|
|
||||||
Dictionary<Uuid, TypeDef> typeDefs = new Dictionary<Uuid, TypeDef>();
|
Dictionary<Uuid, TypeDef> typeDefs = new Dictionary<Uuid, TypeDef>();
|
||||||
|
|
||||||
|
object typeDefsLock = new object();
|
||||||
|
|
||||||
KeyList<uint, AsyncReply> requests = new KeyList<uint, AsyncReply>();
|
KeyList<uint, AsyncReply> requests = new KeyList<uint, AsyncReply>();
|
||||||
|
|
||||||
volatile uint callbackCounter = 0;
|
volatile int callbackCounter = 0;
|
||||||
|
|
||||||
Dictionary<IResource, List<byte>> subscriptions = new Dictionary<IResource, List<byte>>();
|
Dictionary<IResource, List<byte>> subscriptions = new Dictionary<IResource, List<byte>>();
|
||||||
|
|
||||||
@@ -73,15 +75,18 @@ partial class EpConnection
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send IIP request.
|
/// Send EP request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">Packet action.</param>
|
/// <param name="action">Packet action.</param>
|
||||||
/// <param name="args">Arguments to send.</param>
|
/// <param name="args">Arguments to send.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
///
|
||||||
|
|
||||||
AsyncReply SendRequest(EpPacketRequest action, params object[] args)
|
AsyncReply SendRequest(EpPacketRequest action, params object[] args)
|
||||||
{
|
{
|
||||||
var reply = new AsyncReply();
|
var reply = new AsyncReply();
|
||||||
var c = callbackCounter++; // avoid thread racing
|
var c = (uint)Interlocked.Increment(ref callbackCounter);
|
||||||
|
//callbackCounter++; // avoid thread racing
|
||||||
requests.Add(c, reply);
|
requests.Add(c, reply);
|
||||||
|
|
||||||
if (args.Length == 0)
|
if (args.Length == 0)
|
||||||
@@ -112,7 +117,7 @@ partial class EpConnection
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send IIP notification.
|
/// Send EP notification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="action">Packet action.</param>
|
/// <param name="action">Packet action.</param>
|
||||||
/// <param name="args">Arguments to send.</param>
|
/// <param name="args">Arguments to send.</param>
|
||||||
@@ -343,7 +348,7 @@ partial class EpConnection
|
|||||||
var args = DataDeserializer.ListParser(dataType, Instance.Warehouse)
|
var args = DataDeserializer.ListParser(dataType, Instance.Warehouse)
|
||||||
as object[];
|
as object[];
|
||||||
|
|
||||||
var errorCode = (ushort)args[0];
|
var errorCode =Convert.ToUInt16( args[0]);
|
||||||
var errorMsg = (string)args[1];
|
var errorMsg = (string)args[1];
|
||||||
|
|
||||||
req.TriggerError(new AsyncException(type, errorCode, errorMsg));
|
req.TriggerError(new AsyncException(type, errorCode, errorMsg));
|
||||||
@@ -1720,6 +1725,8 @@ partial class EpConnection
|
|||||||
/// <param name="typeId">Type UUID.</param>
|
/// <param name="typeId">Type UUID.</param>
|
||||||
/// <returns>TypeSchema.</returns>
|
/// <returns>TypeSchema.</returns>
|
||||||
public AsyncReply<TypeDef> GetTypeDefById(Uuid typeId)
|
public AsyncReply<TypeDef> GetTypeDefById(Uuid typeId)
|
||||||
|
{
|
||||||
|
lock (typeDefsLock)
|
||||||
{
|
{
|
||||||
if (typeDefs.ContainsKey(typeId))
|
if (typeDefs.ContainsKey(typeId))
|
||||||
return new AsyncReply<TypeDef>(typeDefs[typeId]);
|
return new AsyncReply<TypeDef>(typeDefs[typeId]);
|
||||||
@@ -1745,9 +1752,12 @@ partial class EpConnection
|
|||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public AsyncReply<TypeDef> GetTypeDefByName(string typeName)
|
public AsyncReply<TypeDef> GetTypeDefByName(string typeName)
|
||||||
|
{
|
||||||
|
lock (typeDefsLock)
|
||||||
{
|
{
|
||||||
var typeDef = typeDefs.Values.FirstOrDefault(x => x.Name == typeName);
|
var typeDef = typeDefs.Values.FirstOrDefault(x => x.Name == typeName);
|
||||||
if (typeDef != null)
|
if (typeDef != null)
|
||||||
@@ -1776,6 +1786,7 @@ partial class EpConnection
|
|||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IStore interface
|
// IStore interface
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1846,9 +1857,12 @@ partial class EpConnection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">Resource Id</param>
|
/// <param name="id">Resource Id</param>
|
||||||
/// <returns>DistributedResource</returns>
|
/// <returns>DistributedResource</returns>
|
||||||
|
///
|
||||||
|
object fetchLock = new object();
|
||||||
public AsyncReply<EpResource> Fetch(uint id, uint[] requestSequence)
|
public AsyncReply<EpResource> Fetch(uint id, uint[] requestSequence)
|
||||||
{
|
{
|
||||||
|
//lock (fetchLock)
|
||||||
|
//{
|
||||||
EpResource resource = null;
|
EpResource resource = null;
|
||||||
|
|
||||||
attachedResources[id]?.TryGetTarget(out resource);
|
attachedResources[id]?.TryGetTarget(out resource);
|
||||||
@@ -1979,6 +1993,8 @@ partial class EpConnection
|
|||||||
{
|
{
|
||||||
if (resource == null)
|
if (resource == null)
|
||||||
{
|
{
|
||||||
|
dr.ResourceDefinition = typeDef;
|
||||||
|
|
||||||
Instance.Warehouse.Put(this.Instance.Link + "/" + id.ToString(), dr)
|
Instance.Warehouse.Put(this.Instance.Link + "/" + id.ToString(), dr)
|
||||||
.Then(initResource).Error((ex) => reply.TriggerError(ex));
|
.Then(initResource).Error((ex) => reply.TriggerError(ex));
|
||||||
}
|
}
|
||||||
@@ -1996,6 +2012,7 @@ partial class EpConnection
|
|||||||
|
|
||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -117,30 +117,6 @@ namespace Esiur.Proxy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// var code = @$"using Esiur.Resource;
|
|
||||||
//using Esiur.Core;
|
|
||||||
|
|
||||||
//#nullable enable
|
|
||||||
|
|
||||||
//namespace {ci.ClassSymbol.ContainingNamespace.ToDisplayString()} {{
|
|
||||||
//";
|
|
||||||
|
|
||||||
// if (IsInterfaceImplemented(ci, classes))
|
|
||||||
// code += $"public partial class {ci.Name} {{\r\n";
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// code +=
|
|
||||||
//$@" public partial class {ci.Name} : IResource {{
|
|
||||||
// public virtual Instance? Instance {{ get; set; }}
|
|
||||||
// public virtual event DestroyedEvent? OnDestroy;
|
|
||||||
|
|
||||||
// public virtual void Destroy() {{ OnDestroy?.Invoke(this); }}
|
|
||||||
//";
|
|
||||||
|
|
||||||
// if (!ci.HasTrigger)
|
|
||||||
// code += "\tpublic virtual AsyncReply<bool> Trigger(ResourceTrigger trigger) => new AsyncReply<bool>(true);\r\n\r\n";
|
|
||||||
// }
|
|
||||||
|
|
||||||
foreach (var f in ci.Fields)
|
foreach (var f in ci.Fields)
|
||||||
{
|
{
|
||||||
var givenName = f.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == "ExportAttribute")?.ConstructorArguments.FirstOrDefault().Value as string;
|
var givenName = f.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == "ExportAttribute")?.ConstructorArguments.FirstOrDefault().Value as string;
|
||||||
@@ -156,12 +132,17 @@ namespace Esiur.Proxy
|
|||||||
|
|
||||||
if (f.Type.Name.StartsWith("ResourceEventHandler") || f.Type.Name.StartsWith("CustomResourceEventHandler"))
|
if (f.Type.Name.StartsWith("ResourceEventHandler") || f.Type.Name.StartsWith("CustomResourceEventHandler"))
|
||||||
{
|
{
|
||||||
code.AppendLine($"public event {f.Type} {pn};");
|
code.AppendLine($"\tpublic event {f.Type} {pn};");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
code.AppendLine($"\t{attrs}\r\n\t public {f.Type} {pn} {{ \r\n\t\t get => {fn}; \r\n\t\t set {{ \r\n\t\t this.{fn} = value; \r\n\t\t Instance?.Modified(); \r\n\t\t}}\r\n\t}}\r\n");
|
code.AppendLine($"\tpublic {f.Type} {pn} {{");
|
||||||
|
code.AppendLine($"\t\t get => {fn};");
|
||||||
|
code.AppendLine($"\t\t set {{");
|
||||||
|
code.AppendLine($"\t\t this.{fn} = value;");
|
||||||
|
code.AppendLine($"\t\t Instance?.Modified();");
|
||||||
|
code.AppendLine($"\t\t}}");
|
||||||
|
code.AppendLine($"\t}}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Esiur.Proxy;
|
|||||||
public static class ResourceProxy
|
public static class ResourceProxy
|
||||||
{
|
{
|
||||||
static Dictionary<Type, Type> cache = new Dictionary<Type, Type>();
|
static Dictionary<Type, Type> cache = new Dictionary<Type, Type>();
|
||||||
|
static object cacheLock = new object();
|
||||||
|
|
||||||
#if NETSTANDARD
|
#if NETSTANDARD
|
||||||
static MethodInfo modifyMethod = typeof(Instance).GetTypeInfo().GetMethod("Modified");
|
static MethodInfo modifyMethod = typeof(Instance).GetTypeInfo().GetMethod("Modified");
|
||||||
@@ -48,7 +49,8 @@ public static class ResourceProxy
|
|||||||
|
|
||||||
public static Type GetProxy(Type type)
|
public static Type GetProxy(Type type)
|
||||||
{
|
{
|
||||||
|
lock (cacheLock)
|
||||||
|
{
|
||||||
if (cache.ContainsKey(type))
|
if (cache.ContainsKey(type))
|
||||||
return cache[type];
|
return cache[type];
|
||||||
|
|
||||||
@@ -114,6 +116,7 @@ public static class ResourceProxy
|
|||||||
return t;
|
return t;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Type GetProxy<T>()
|
public static Type GetProxy<T>()
|
||||||
where T : IResource
|
where T : IResource
|
||||||
|
|||||||
@@ -753,7 +753,7 @@ public class Instance
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.definition = Warehouse.GetTypeDefByType(resource.GetType());
|
this.definition = warehouse.GetTypeDefByType(resource.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
// set ages
|
// set ages
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ public class Warehouse
|
|||||||
[TypeDefKind.Enum] = new KeyList<Uuid, TypeDef>(),
|
[TypeDefKind.Enum] = new KeyList<Uuid, TypeDef>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
object typeDefsLock = new object();
|
||||||
|
|
||||||
bool warehouseIsOpen = false;
|
bool warehouseIsOpen = false;
|
||||||
|
|
||||||
public delegate void StoreEvent(IStore store);
|
public delegate void StoreEvent(IStore store);
|
||||||
@@ -507,12 +509,15 @@ public class Warehouse
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="typeDef">Resource type definition.</param>
|
/// <param name="typeDef">Resource type definition.</param>
|
||||||
public void RegisterTypeDef(TypeDef typeDef)
|
public void RegisterTypeDef(TypeDef typeDef)
|
||||||
|
{
|
||||||
|
lock (typeDefsLock)
|
||||||
{
|
{
|
||||||
if (typeDefs[typeDef.Kind].ContainsKey(typeDef.Id))
|
if (typeDefs[typeDef.Kind].ContainsKey(typeDef.Id))
|
||||||
throw new Exception($"TypeDef with same class Id already exists. {typeDefs[typeDef.Kind][typeDef.Id].Name} -> {typeDef.Name}");
|
throw new Exception($"TypeDef with same class Id already exists. {typeDefs[typeDef.Kind][typeDef.Id].Name} -> {typeDef.Name}");
|
||||||
|
|
||||||
typeDefs[typeDef.Kind][typeDef.Id] = typeDef;
|
typeDefs[typeDef.Kind][typeDef.Id] = typeDef;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -522,9 +527,11 @@ public class Warehouse
|
|||||||
/// <returns>Resource TypeDef.</returns>
|
/// <returns>Resource TypeDef.</returns>
|
||||||
public TypeDef GetTypeDefByType(Type type)
|
public TypeDef GetTypeDefByType(Type type)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!(type.IsClass || type.IsEnum))
|
if (!(type.IsClass || type.IsEnum))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
||||||
var baseType = ResourceProxy.GetBaseType(type);
|
var baseType = ResourceProxy.GetBaseType(type);
|
||||||
|
|
||||||
if (baseType == typeof(IResource)
|
if (baseType == typeof(IResource)
|
||||||
@@ -541,6 +548,8 @@ public class Warehouse
|
|||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
lock (typeDefsLock)
|
||||||
|
{
|
||||||
var typeDef = typeDefs[typeDefKind].Values.FirstOrDefault(x => x.DefinedType == baseType);
|
var typeDef = typeDefs[typeDefKind].Values.FirstOrDefault(x => x.DefinedType == baseType);
|
||||||
if (typeDef != null)
|
if (typeDef != null)
|
||||||
return typeDef;
|
return typeDef;
|
||||||
@@ -548,9 +557,9 @@ public class Warehouse
|
|||||||
// create new TypeDef for type
|
// create new TypeDef for type
|
||||||
typeDef = new TypeDef(baseType, this);
|
typeDef = new TypeDef(baseType, this);
|
||||||
TypeDef.GetDependencies(typeDef, this);
|
TypeDef.GetDependencies(typeDef, this);
|
||||||
|
|
||||||
return typeDef;
|
return typeDef;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a TypeDef by TypeId from the warehouse. If not in the warehouse, a new TypeDef is created and added to the warehouse.
|
/// Get a TypeDef by TypeId from the warehouse. If not in the warehouse, a new TypeDef is created and added to the warehouse.
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
|
<ProjectReference Include="..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
+15
-12
@@ -17,9 +17,9 @@
|
|||||||
// Usage (client only): dotnet run -- --mode client --host 127.0.0.1 --concurrent 50 --resources 200
|
// Usage (client only): dotnet run -- --mode client --host 127.0.0.1 --concurrent 50 --resources 200
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
using Esiur.Protocol;
|
||||||
using Esiur.Resource;
|
using Esiur.Resource;
|
||||||
using Esiur.Stores;
|
using Esiur.Stores;
|
||||||
using Esiur.Protocol;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
var mode = GetArg(args, "--mode", "both");
|
var mode = GetArg(args, "--mode", "both");
|
||||||
@@ -29,30 +29,30 @@ var concurrent = int.Parse(GetArg(args, "--concurrent", "50"));
|
|||||||
var resources = int.Parse(GetArg(args, "--resources", "200"));
|
var resources = int.Parse(GetArg(args, "--resources", "200"));
|
||||||
var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000"));
|
var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000"));
|
||||||
var rounds = int.Parse(GetArg(args, "--rounds", "5"));
|
var rounds = int.Parse(GetArg(args, "--rounds", "5"));
|
||||||
var wh = new Warehouse();
|
|
||||||
|
|
||||||
|
var clientWh = new Warehouse();
|
||||||
|
var serverWh = new Warehouse();
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
// SERVER SIDE
|
// SERVER SIDE
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
if (mode == "server" || mode == "both")
|
if (mode == "server" || mode == "both")
|
||||||
{
|
{
|
||||||
|
await serverWh.Put("sys", new MemoryStore());
|
||||||
await wh.Put("sys", new MemoryStore());
|
await serverWh.Put("sys/server", new EpServer() { Port = (ushort)port });
|
||||||
await wh.Put("sys/server", new EpServer() { Port = (ushort)port });
|
|
||||||
|
|
||||||
for (int i = 0; i < resources; i++)
|
for (int i = 0; i < resources; i++)
|
||||||
{
|
{
|
||||||
await wh.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i });
|
await serverWh.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i });
|
||||||
}
|
}
|
||||||
|
|
||||||
await wh.Open();
|
await serverWh.Open();
|
||||||
Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}");
|
Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}");
|
||||||
|
|
||||||
if (mode == "server")
|
if (mode == "server")
|
||||||
{
|
{
|
||||||
Console.WriteLine("Press ENTER to stop.");
|
Console.WriteLine("Press ENTER to stop.");
|
||||||
Console.ReadLine();
|
Console.ReadLine();
|
||||||
await wh.Close();
|
await serverWh.Close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,8 +90,8 @@ for (int round = 0; round < rounds; round++)
|
|||||||
using var cts = new CancellationTokenSource(timeoutMs);
|
using var cts = new CancellationTokenSource(timeoutMs);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var proxy = await wh.Get<IResource>(
|
var proxy = await clientWh.Get<IResource>(
|
||||||
$"iip://{host}:{port}/sys/sensor_{resourceIdx}");
|
$"ep://{host}:{port}/sys/sensor_{resourceIdx}");
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
latencies[taskIdx] = sw.Elapsed.TotalMilliseconds;
|
latencies[taskIdx] = sw.Elapsed.TotalMilliseconds;
|
||||||
@@ -161,8 +161,11 @@ var csv = "round,concurrent,succeeded,failed,timed_out,total_wall_ms,min_ms,p50_
|
|||||||
await File.WriteAllTextAsync("test3_concurrent_attach.csv", csv);
|
await File.WriteAllTextAsync("test3_concurrent_attach.csv", csv);
|
||||||
Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv");
|
Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv");
|
||||||
|
|
||||||
if (mode == "both")
|
if (mode == "server" || mode == "both")
|
||||||
await wh.Close();
|
await serverWh.Close();
|
||||||
|
|
||||||
|
if (mode == "client" || mode == "both")
|
||||||
|
await clientWh.Close();
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\..\..\Libraries\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
// ============================================================
|
|
||||||
// Test 3: Concurrent Attachments — COMBINED (server + clients
|
|
||||||
// in the same process for local stress testing, or run the
|
|
||||||
// server section separately for multi-machine testing).
|
|
||||||
//
|
|
||||||
// Fires N concurrent Warehouse.Get calls simultaneously and
|
|
||||||
// measures:
|
|
||||||
// - Time for all proxies to reach Ready state
|
|
||||||
// - Whether any attachments fail or deadlock (timeout)
|
|
||||||
// - Distribution of per-attachment latency
|
|
||||||
//
|
|
||||||
// This directly stress-tests Algorithm 1 (FETCH-RESOURCE) and
|
|
||||||
// the parallel deadlock detection mechanism from Section V.D.
|
|
||||||
//
|
|
||||||
// Usage (single process): dotnet run -- --mode both --concurrent 50 --resources 200
|
|
||||||
// Usage (server only): dotnet run -- --mode server --resources 200 --port 10902
|
|
||||||
// Usage (client only): dotnet run -- --mode client --host 127.0.0.1 --concurrent 50 --resources 200
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
using Esiur.Protocol;
|
|
||||||
using Esiur.Resource;
|
|
||||||
using Esiur.Stores;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
var mode = GetArg(args, "--mode", "both");
|
|
||||||
var host = GetArg(args, "--host", "127.0.0.1");
|
|
||||||
var port = int.Parse(GetArg(args, "--port", "10902"));
|
|
||||||
var concurrent = int.Parse(GetArg(args, "--concurrent", "50"));
|
|
||||||
var resources = int.Parse(GetArg(args, "--resources", "200"));
|
|
||||||
var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000"));
|
|
||||||
var rounds = int.Parse(GetArg(args, "--rounds", "5"));
|
|
||||||
|
|
||||||
var wh = new Warehouse();
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// SERVER SIDE
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
if (mode == "server" || mode == "both")
|
|
||||||
{
|
|
||||||
await wh.Put("sys", new MemoryStore());
|
|
||||||
await wh.Put("sys/server", new EpServer() { Port = (ushort)port });
|
|
||||||
|
|
||||||
for (int i = 0; i < resources; i++)
|
|
||||||
{
|
|
||||||
await wh.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i });
|
|
||||||
}
|
|
||||||
|
|
||||||
await wh.Open();
|
|
||||||
Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}");
|
|
||||||
|
|
||||||
if (mode == "server")
|
|
||||||
{
|
|
||||||
Console.WriteLine("Press ENTER to stop.");
|
|
||||||
Console.ReadLine();
|
|
||||||
await wh.Close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give server a moment to fully initialise before client fires
|
|
||||||
await Task.Delay(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// CLIENT SIDE
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
Console.WriteLine($"[Client-T3] concurrent={concurrent} resources={resources} rounds={rounds}");
|
|
||||||
|
|
||||||
var roundResults = new List<RoundResult>();
|
|
||||||
|
|
||||||
for (int round = 0; round < rounds; round++)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"\n[Client-T3] Round {round + 1}/{rounds}");
|
|
||||||
|
|
||||||
// Pick `concurrent` random resource indices (may overlap — intentional,
|
|
||||||
// because overlapping triggers the "already in progress" path of Algorithm 1)
|
|
||||||
var rng = new Random(round);
|
|
||||||
var targets = Enumerable.Range(0, concurrent)
|
|
||||||
.Select(_ => rng.Next(resources))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
long succeeded = 0, failed = 0, timedOut = 0;
|
|
||||||
var latencies = new double[concurrent];
|
|
||||||
|
|
||||||
var roundSw = Stopwatch.StartNew();
|
|
||||||
|
|
||||||
// Fire all attachments simultaneously
|
|
||||||
var tasks = targets.Select((resourceIdx, taskIdx) => Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var sw = Stopwatch.StartNew();
|
|
||||||
using var cts = new CancellationTokenSource(timeoutMs);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var proxy = await wh.Get<IResource>(
|
|
||||||
$"iip://{host}:{port}/sys/sensor_{resourceIdx}");
|
|
||||||
|
|
||||||
sw.Stop();
|
|
||||||
latencies[taskIdx] = sw.Elapsed.TotalMilliseconds;
|
|
||||||
|
|
||||||
if (proxy != null)
|
|
||||||
Interlocked.Increment(ref succeeded);
|
|
||||||
else
|
|
||||||
Interlocked.Increment(ref failed);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
sw.Stop();
|
|
||||||
latencies[taskIdx] = timeoutMs;
|
|
||||||
Interlocked.Increment(ref timedOut);
|
|
||||||
Console.WriteLine($" [!] Timeout on sensor_{resourceIdx} after {timeoutMs}ms");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
sw.Stop();
|
|
||||||
latencies[taskIdx] = sw.Elapsed.TotalMilliseconds;
|
|
||||||
Interlocked.Increment(ref failed);
|
|
||||||
Console.WriteLine($" [!] Error on sensor_{resourceIdx}: {ex.Message}");
|
|
||||||
}
|
|
||||||
})).ToArray();
|
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
roundSw.Stop();
|
|
||||||
|
|
||||||
var sorted = latencies.OrderBy(x => x).ToArray();
|
|
||||||
int n = sorted.Length;
|
|
||||||
|
|
||||||
var result = new RoundResult
|
|
||||||
{
|
|
||||||
Round = round + 1,
|
|
||||||
Concurrent = concurrent,
|
|
||||||
Succeeded = succeeded,
|
|
||||||
Failed = failed,
|
|
||||||
TimedOut = timedOut,
|
|
||||||
TotalMs = roundSw.Elapsed.TotalMilliseconds,
|
|
||||||
MinMs = sorted[0],
|
|
||||||
P50Ms = sorted[(int)(n * 0.50)],
|
|
||||||
P95Ms = sorted[(int)(n * 0.95)],
|
|
||||||
P99Ms = sorted[(int)(n * 0.99)],
|
|
||||||
MaxMs = sorted[n - 1],
|
|
||||||
MeanMs = sorted.Average()
|
|
||||||
};
|
|
||||||
roundResults.Add(result);
|
|
||||||
|
|
||||||
Console.WriteLine($" succeeded={succeeded}/{concurrent} failed={failed} timedOut={timedOut}");
|
|
||||||
Console.WriteLine($" total_wall={result.TotalMs:F0}ms");
|
|
||||||
Console.WriteLine($" latency: min={result.MinMs:F1} p50={result.P50Ms:F1} p95={result.P95Ms:F1} " +
|
|
||||||
$"p99={result.P99Ms:F1} max={result.MaxMs:F1} mean={result.MeanMs:F1} (ms)");
|
|
||||||
|
|
||||||
// Release all proxies before next round to reset attachment state
|
|
||||||
GC.Collect();
|
|
||||||
await Task.Delay(1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// CSV output
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
var csv = "round,concurrent,succeeded,failed,timed_out,total_wall_ms,min_ms,p50_ms,p95_ms,p99_ms,max_ms,mean_ms\n" +
|
|
||||||
string.Join("\n", roundResults.Select(r =>
|
|
||||||
$"{r.Round},{r.Concurrent},{r.Succeeded},{r.Failed},{r.TimedOut}," +
|
|
||||||
$"{r.TotalMs:F1},{r.MinMs:F2},{r.P50Ms:F2},{r.P95Ms:F2},{r.P99Ms:F2},{r.MaxMs:F2},{r.MeanMs:F2}"));
|
|
||||||
|
|
||||||
await File.WriteAllTextAsync("test3_concurrent_attach.csv", csv);
|
|
||||||
Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv");
|
|
||||||
|
|
||||||
if (mode == "both")
|
|
||||||
await wh.Close();
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
// Helpers
|
|
||||||
// ----------------------------------------------------------------
|
|
||||||
static string GetArg(string[] args, string key, string def)
|
|
||||||
{
|
|
||||||
int i = Array.IndexOf(args, key);
|
|
||||||
return (i >= 0 && i + 1 < args.Length) ? args[i + 1] : def;
|
|
||||||
}
|
|
||||||
|
|
||||||
record RoundResult
|
|
||||||
{
|
|
||||||
public int Round;
|
|
||||||
public int Concurrent;
|
|
||||||
public long Succeeded, Failed, TimedOut;
|
|
||||||
public double TotalMs, MinMs, P50Ms, P95Ms, P99Ms, MaxMs, MeanMs;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using Esiur.Resource;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shared observable sensor resource used across all scalability tests.
|
|
||||||
/// Property changes via Value setter are automatically propagated
|
|
||||||
/// to all attached remote peers by the Esiur runtime.
|
|
||||||
/// </summary>
|
|
||||||
[Resource]
|
|
||||||
public partial class SensorResource : Resource
|
|
||||||
{
|
|
||||||
public int SensorId { get; set; }
|
|
||||||
|
|
||||||
[Export]
|
|
||||||
public double value;
|
|
||||||
}
|
|
||||||
@@ -29,7 +29,7 @@ long latencySamples = 0;
|
|||||||
var latencyLock = new object();
|
var latencyLock = new object();
|
||||||
|
|
||||||
// --- Attach all resources -------------------------------------------
|
// --- Attach all resources -------------------------------------------
|
||||||
var proxies = new dynamic[resourceCount];
|
var proxies = new IResource[resourceCount];
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
var wh = new Warehouse();
|
var wh = new Warehouse();
|
||||||
@@ -38,16 +38,18 @@ try
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < resourceCount; i++)
|
for (int i = 0; i < resourceCount; i++)
|
||||||
{
|
{
|
||||||
proxies[i] = await wh.Get<IResource>($"iip://{host}:{port}/sys/sensor_{i}");
|
proxies[i] = await wh.Get<IResource>($"ep://{host}:{port}/sys/sensor_{i}");
|
||||||
|
|
||||||
|
dynamic resource = proxies[i];
|
||||||
|
|
||||||
// Subscribe to property change notifications via the Esiur event model
|
// Subscribe to property change notifications via the Esiur event model
|
||||||
double lastValue = (double)proxies[i].Value;
|
double lastValue = (double)resource.Value;
|
||||||
long lastTick = Stopwatch.GetTimestamp();
|
long lastTick = Stopwatch.GetTimestamp();
|
||||||
int capturedI = i;
|
int capturedI = i;
|
||||||
|
|
||||||
proxies[i].OnPropertyModified += (string propName, object oldVal, object newVal) =>
|
proxies[i].Instance.PropertyModified += (PropertyModificationInfo data) =>
|
||||||
{
|
{
|
||||||
if (propName != "Value") return;
|
if (data.Name != "Value") return;
|
||||||
|
|
||||||
long nowTick = Stopwatch.GetTimestamp();
|
long nowTick = Stopwatch.GetTimestamp();
|
||||||
double elapsedMs = (nowTick - lastTick) * 1000.0 / Stopwatch.Frequency;
|
double elapsedMs = (nowTick - lastTick) * 1000.0 / Stopwatch.Frequency;
|
||||||
@@ -73,6 +75,7 @@ catch (Exception ex)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- Measurement window ---------------------------------------------
|
// --- Measurement window ---------------------------------------------
|
||||||
sw.Restart();
|
sw.Restart();
|
||||||
long lastReceived = 0;
|
long lastReceived = 0;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
// Usage: dotnet run -- --host 127.0.0.1 --port 10901 --resources 10000
|
// Usage: dotnet run -- --host 127.0.0.1 --port 10901 --resources 10000
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
using Esiur.Protocol;
|
||||||
using Esiur.Resource;
|
using Esiur.Resource;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
@@ -18,14 +19,19 @@ var batchSize = int.Parse(GetArg(args, "--batch", "100"));
|
|||||||
|
|
||||||
Console.WriteLine($"[Client-T2] Connecting to {host}:{port}, resources={resourceCount}");
|
Console.WriteLine($"[Client-T2] Connecting to {host}:{port}, resources={resourceCount}");
|
||||||
|
|
||||||
|
var wh = new Warehouse();
|
||||||
|
|
||||||
|
var connnection = await wh.Get<EpConnection>(
|
||||||
|
$"ep://{host}:{port}");
|
||||||
|
|
||||||
var attachLatencies = new List<double>(resourceCount);
|
var attachLatencies = new List<double>(resourceCount);
|
||||||
var proxies = new dynamic[resourceCount];
|
var proxies = new IResource[resourceCount];
|
||||||
|
|
||||||
// --- Attach in batches to avoid overwhelming the runtime -------------
|
// --- Attach in batches to avoid overwhelming the runtime -------------
|
||||||
var totalSw = Stopwatch.StartNew();
|
var totalSw = Stopwatch.StartNew();
|
||||||
var wh = new Warehouse();
|
|
||||||
for (int batch = 0; batch < resourceCount; batch += batchSize)
|
for (int batch = 0; batch < resourceCount; batch += batchSize)
|
||||||
{
|
{
|
||||||
|
|
||||||
int end = Math.Min(batch + batchSize, resourceCount);
|
int end = Math.Min(batch + batchSize, resourceCount);
|
||||||
var batchTasks = new Task[end - batch];
|
var batchTasks = new Task[end - batch];
|
||||||
|
|
||||||
@@ -35,8 +41,8 @@ for (int batch = 0; batch < resourceCount; batch += batchSize)
|
|||||||
batchTasks[i - batch] = Task.Run(async () =>
|
batchTasks[i - batch] = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
proxies[capturedI] = await wh.Get<IResource>(
|
proxies[capturedI] = await connnection.Get($"sys/sensor_{capturedI}");
|
||||||
$"iip://{host}:{port}/sys/sensor_{capturedI}");
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
||||||
lock (attachLatencies)
|
lock (attachLatencies)
|
||||||
@@ -45,7 +51,7 @@ for (int batch = 0; batch < resourceCount; batch += batchSize)
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(batchTasks);
|
await Task.WhenAll(batchTasks);
|
||||||
|
//Console.WriteLine("D");
|
||||||
if (batch % 1000 == 0)
|
if (batch % 1000 == 0)
|
||||||
Console.WriteLine($"[Client-T2] Attached {Math.Min(batch + batchSize, resourceCount)}/{resourceCount} " +
|
Console.WriteLine($"[Client-T2] Attached {Math.Min(batch + batchSize, resourceCount)}/{resourceCount} " +
|
||||||
$"elapsed={totalSw.Elapsed.TotalSeconds:F1}s");
|
$"elapsed={totalSw.Elapsed.TotalSeconds:F1}s");
|
||||||
@@ -74,13 +80,15 @@ double sumLatencyMs = 0;
|
|||||||
for (int i = 0; i < Math.Min(500, resourceCount); i++)
|
for (int i = 0; i < Math.Min(500, resourceCount); i++)
|
||||||
{
|
{
|
||||||
int capturedI = i;
|
int capturedI = i;
|
||||||
proxies[capturedI].OnPropertyModified += (string propName, object oldVal, object newVal) =>
|
proxies[capturedI].Instance.PropertyModified += (PropertyModificationInfo data) =>
|
||||||
{
|
{
|
||||||
if (propName == "Value")
|
if (data.Name == "Value")
|
||||||
Interlocked.Increment(ref received);
|
Interlocked.Increment(ref received);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await connnection.Call("UpdateValues");
|
||||||
|
|
||||||
await Task.Delay(10000); // observe for 10s
|
await Task.Delay(10000); // observe for 10s
|
||||||
Console.WriteLine($"[Client-T2] Received {received} notifications in 10s from first 500 resources");
|
Console.WriteLine($"[Client-T2] Received {received} notifications in 10s from first 500 resources");
|
||||||
|
|
||||||
|
|||||||
@@ -18,18 +18,29 @@ Console.WriteLine($"[Server-T2] Creating {resourceCount} resources on port {port
|
|||||||
var wh = new Warehouse();
|
var wh = new Warehouse();
|
||||||
|
|
||||||
await wh.Put("sys", new MemoryStore());
|
await wh.Put("sys", new MemoryStore());
|
||||||
await wh.Put("sys/server", new EpServer() { Port = (ushort)port });
|
var server = await wh.Put("sys/server", new EpServer() { Port = (ushort)port });
|
||||||
|
|
||||||
long memBefore = GC.GetTotalMemory(forceFullCollection: true);
|
long memBefore = GC.GetTotalMemory(forceFullCollection: true);
|
||||||
|
|
||||||
|
List<SensorResource> sensors = new List<SensorResource>();
|
||||||
|
|
||||||
for (int i = 0; i < resourceCount; i++)
|
for (int i = 0; i < resourceCount; i++)
|
||||||
{
|
{
|
||||||
var s = new SensorResource { SensorId = i, Value = i * 0.1 };
|
var sensor = await wh.Put($"sys/sensor_{i}",
|
||||||
await wh.Put($"sys/sensor_{i}", s);
|
new SensorResource { SensorId = i, Value = i * 0.1 });
|
||||||
|
sensors.Add(sensor);
|
||||||
}
|
}
|
||||||
|
|
||||||
await wh.Open();
|
await wh.Open();
|
||||||
|
|
||||||
|
server.MapCall("UpdateValues", () =>
|
||||||
|
{
|
||||||
|
foreach(var sensor in sensors)
|
||||||
|
{
|
||||||
|
sensor.Value += 0.1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
long memAfter = GC.GetTotalMemory(forceFullCollection: true);
|
long memAfter = GC.GetTotalMemory(forceFullCollection: true);
|
||||||
double memMB = (memAfter - memBefore) / (1024.0 * 1024.0);
|
double memMB = (memAfter - memBefore) / (1024.0 * 1024.0);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user