/// <param name="client">The client that will send the message.</param> /// <param name="m">The monitor used to log.</param> /// <param name="message">The message that will be sent. It will be disposed after being stored.</param> /// <returns>A ValueTask, that complete when the message has been stored, containing a Task that complete when the publish has been ack. /// On QoS 0, the message is directly sent and the returned Task is Task.CompletedTask. /// </returns> public static async ValueTask <Task> PublishAsync(this IMqtt3Client client, IActivityMonitor?m, DisposableApplicationMessage message) { Task task = await client.PublishAsync(m, message.Topic, message.QoS, message.Retain, message.Payload); //The packet has been stored message.Dispose(); // So we can dispose after it has been stored. return(task); // The task we return complete when the packet has been acked. }
/// <summary> /// Gets all the <see cref="StObjMapInfo"/> available in all the loaded assemblies. /// This method like all the methods that manipulates StObjMapInfo are thread/concurrency safe. /// </summary> /// <returns>An array of all the available informations.</returns> public static StObjMapInfo[] GetAvailableMapInfos(IActivityMonitor?monitor = null) { lock ( _alreadyHandled ) { return(LockedGetAvailableMapInfos(ref monitor).ToArray()); } }
/// <summary> /// Create a <see cref="TcpChannel"/>. The connection string should be "hostname:port". /// </summary> /// <param name="m">The logger to use.</param> /// <param name="connectionString">"hostname:port"</param> /// <returns></returns> public ValueTask <IMqttChannel> CreateAsync(IActivityMonitor?m, string connectionString) { string[] strs = connectionString.Split(':'); TcpClient client = new(strs[0], int.Parse(strs[1])); client.NoDelay = true; return(new ValueTask <IMqttChannel>(new TcpChannel(client))); }
/// <summary> /// Tries to get a <see cref="StObjMapInfo"/> from its signature among all loaded assemblies. /// This never throws: errors are logged (a new monitor is automatically managed when <paramref name="monitor"/> is null), /// and null is returned. /// This method like all the methods that manipulates StObjMapInfo are thread/concurrency safe. /// </summary> /// <param name="signature">Signature to find.</param> /// <param name="monitor">Optional monitor.</param> /// <returns>The StObjMapInfo if it exists.</returns> public static StObjMapInfo?GetMapInfo(SHA1Value signature, IActivityMonitor?monitor = null) { lock ( _alreadyHandled ) { LockedGetAvailableMapInfos(ref monitor); return(_alreadyHandled.GetValueOrDefault(signature.ToString())); } }
protected void OpenPumps(IActivityMonitor?m, T newState) { using (m?.OpenInfo("Opening pumps.")) { Open(); _state = newState; } }
/// <summary> /// Tries to get a <see cref="StObjMapInfo"/> from an assembly. /// This never throws: errors are logged (a new monitor is automatically managed when <paramref name="monitor"/> is null), /// and null is returned. /// This method like all the methods that manipulates StObjMapInfo are thread/concurrency safe. /// </summary> /// <param name="a">Candidate assembly.</param> /// <param name="monitor">Optional monitor.</param> /// <returns>A <see cref="IStObjMap"/> that provides access to the objects graph.</returns> public static StObjMapInfo?GetMapInfo(Assembly a, IActivityMonitor?monitor = null) { if (a == null) { throw new ArgumentNullException(nameof(a)); } lock ( _alreadyHandled ) { return(LockedGetMapInfo(a, ref monitor)); } }
public static ValueTask <Task <T?> > SendPacket <T>(IActivityMonitor?m, IOutgoingPacketStore store, OutputPump output, IOutgoingPacket packet) where T : class { IDisposableGroup?group = m?.OpenTrace($"Sending a packet '{packet}'in QoS {packet.Qos}"); return(packet.Qos switch { QualityOfService.AtMostOnce => PublishQoS0 <T>(m, group, output, packet), QualityOfService.AtLeastOnce => StoreAndSend <T>(m, group, output, store, packet, packet.Qos), QualityOfService.ExactlyOnce => StoreAndSend <T>(m, group, output, store, packet, packet.Qos), _ => throw new ArgumentException("Invalid QoS."), });
/// <summary> /// Gets the <see cref="IStObjMap"/> if no error prevents its instantiation from /// the <see cref="StObjMapInfo"/>. /// This never throws: errors are logged (a new monitor is automatically managed when <paramref name="monitor"/> is null), /// and null is returned. /// This method like all the methods that manipulates StObjMapInfo are thread/concurrency safe. /// </summary> /// <param name="info">The info.</param> /// <param name="monitor">Optional monitor.</param> /// <returns>The loaded map or null on error.</returns> public static IStObjMap?GetStObjMap(StObjMapInfo info, IActivityMonitor?monitor = null) { Throw.CheckNotNullArgument(info); if (info.StObjMap != null || info.LoadError != null) { return(info.StObjMap); } lock ( _alreadyHandled ) { return(LockedGetStObjMapFromInfo(info, ref monitor)); } }
/// <summary> /// Attempts to load a StObjMap with a provided signature from all available maps. /// </summary> /// <param name="signature">The signature.</param> /// <param name="monitor">Optional monitor to use.</param> /// <returns>A <see cref="IStObjMap"/> that provides access to the objects graph.</returns> public static IStObjMap?Load(SHA1Value signature, IActivityMonitor?monitor = null) { lock ( _alreadyHandled ) { LockedGetAvailableMapInfos(ref monitor); if (_alreadyHandled.TryGetValue(signature.ToString(), out var info)) { Debug.Assert(info != null); return(LockedGetStObjMapFromInfo(info, ref monitor)); } return(null); } }
public ValueTask <Task <T?> > SendPacket <T>(IActivityMonitor?m, IOutgoingPacket outgoingPacket) where T : class { ClientState?state = State; if (!IsConnected) { throw new InvalidOperationException("Client is Disconnected."); } if (state is null) { throw new NullReferenceException(); } return(SenderHelper.SendPacket <T>(m, state.Store, state.OutputPump, outgoingPacket)); }
static List <StObjMapInfo> LockedGetAvailableMapInfos([NotNullIfNotNull("monitor")] ref IActivityMonitor?monitor) { var all = AppDomain.CurrentDomain.GetAssemblies(); if (all.Length != _allAssemblyCount) { // Don't know/trust the ordering: process them all. foreach (var a in all) { LockedGetMapInfo(a, ref monitor); } _allAssemblyCount = all.Length; } return(_availableMaps); }
/// <summary> /// Attempts to load a StObjMap from an assembly name. /// <para> /// If a <see cref="SuffixSignature"/> file exists and contains a valid signature, the StObjMap is /// loaded from the <see cref="GetAvailableMapInfos(IActivityMonitor?)"/> if it exists. /// </para> /// </summary> /// <param name="assemblyName">The assembly name.</param> /// <param name="monitor">Optional monitor to use.</param> /// <returns>A <see cref="IStObjMap"/> that provides access to the objects graph.</returns> public static IStObjMap?Load(string assemblyName, IActivityMonitor?monitor = null) { Throw.CheckNotNullOrEmptyArgument(assemblyName); Throw.CheckArgument(FileUtil.IndexOfInvalidFileNameChars(assemblyName) < 0); if (!assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && !assemblyName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) { assemblyName = assemblyName + ".dll"; } string assemblyFullPath = Path.Combine(AppContext.BaseDirectory, assemblyName); var signaturePath = assemblyFullPath + SuffixSignature; if (File.Exists(signaturePath) && SHA1Value.TryParse(File.ReadAllText(signaturePath), out var signature)) { var map = Load(signature, monitor); if (map != null) { return(map); } } lock ( _alreadyHandled ) { LockedEnsureMonitor(ref monitor); using (monitor.OpenInfo($"Loading StObj map from '{assemblyName}'.")) { try { // LoadFromAssemblyPath caches the assemblies by their path. // No need to do it. var a = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyFullPath); var info = LockedGetMapInfo(a, ref monitor); if (info == null) { return(null); } return(LockedGetStObjMapFromInfo(info, ref monitor)); } catch (Exception ex) { monitor.Error(ex); return(null); } } } }
/// <summary> /// Replays this monitor's content into another monitor. /// </summary> /// <param name="replay">The target monitor. Can not be null.</param> /// <param name="monitor">Optional monitor (nothing is logged when null).</param> public void Replay(IActivityMonitor replay, IActivityMonitor?monitor = null) { using (monitor?.OpenInfo($"Replaying activity from '{MonitorId}'.")) { int nbMissing = 0; int nbTotal = 0; using (var page = ReadFirstPage(1024)) { foreach (ParentedLogEntry e in page.Entries) { ++nbTotal; LogLevel level = e.Entry.LogLevel; if (e.IsMissing) { ++nbMissing; level = LogLevel.Trace; } switch (e.Entry.LogType) { case LogEntryType.Line: var d = new ActivityMonitorLogData(level, e.Entry.Tags, e.Entry.Text, CKException.CreateFrom(e.Entry.Exception), e.Entry.FileName, e.Entry.LineNumber); d.SetExplicitLogTime(e.Entry.LogTime); replay.UnfilteredLog(ref d); break; case LogEntryType.OpenGroup: d = new ActivityMonitorLogData(level, e.Entry.Tags, e.Entry.Text, CKException.CreateFrom(e.Entry.Exception), e.Entry.FileName, e.Entry.LineNumber); d.SetExplicitLogTime(e.Entry.LogTime); replay.UnfilteredOpenGroup(ref d); break; case LogEntryType.CloseGroup: replay.CloseGroup(e.Entry.Conclusions, e.Entry.LogTime); break; } } page.ForwardPage(); } monitor?.CloseGroup($"Replayed {nbTotal} entries ({nbMissing} missing)."); } }
protected override async ValueTask <IOutgoingPacket> DoStorePacket(IActivityMonitor?m, IOutgoingPacket packet) { int packetSize = packet.GetSize(_protocolConfig.ProtocolLevel); m?.Trace($"Renting {packetSize} bytes to persist {packet}."); IMemoryOwner <byte> memOwner = MemoryPool <byte> .Shared.Rent(packetSize); PipeWriter pipe = PipeWriter.Create(memOwner.Memory.AsStream()); // And write their content to this memory. using (m?.OpenTrace($"Serializing {packet} into memory...")) { if (await packet.WriteAsync(_protocolConfig.ProtocolLevel, pipe, default) != WriteResult.Written) { throw new InvalidOperationException("Didn't wrote packet correctly."); } } Memory <byte> slicedMem = memOwner.Memory.Slice(0, packetSize); base[packet.PacketId].Content.Storage = new StoredPacket(slicedMem, memOwner); return(new FromMemoryOutgoingPacket(slicedMem, packet.Qos, packet.PacketId)); }
static IStObjMap?LockedGetStObjMapFromInfo(StObjMapInfo info, [NotNullIfNotNull("monitor")] ref IActivityMonitor?monitor) { if (info.StObjMap != null || info.LoadError != null) { return(info.StObjMap); } LockedEnsureMonitor(ref monitor); using (monitor.OpenInfo($"Instantiating StObjMap from {info}.")) { try { return(info.StObjMap = (IStObjMap?)Activator.CreateInstance(info.StObjMapType, new object[] { monitor })); } catch (Exception ex) { monitor.Error(ex); info.LoadError = ex.Message; return(null); } } }
/// <summary> /// Called by the external world to explicitly close the connection to the remote. /// </summary> /// <param name="reason">The reason of the disconnection.</param> /// <returns>True if this call actually closed the connection, false if the connection has already been closed by a concurrent decision.</returns> public Task <bool> DisconnectAsync(IActivityMonitor?m, bool clearSession, bool cancelAckTasks) { ClientState?state = State; if (clearSession && !cancelAckTasks) { throw new ArgumentException("When the session is cleared, the ACK tasks must be canceled too."); } if (!IsConnected) { return(Task.FromResult(false)); } if (state is null) { throw new NullReferenceException(); } if (cancelAckTasks) { state !.Store.CancelAllAckTask(m); } return(CloseAsync(DisconnectedReason.UserDisconnected)); }
/// <summary> /// Attempts to load a StObjMap from an assembly. /// </summary> /// <param name="a">Already generated assembly.</param> /// <param name="monitor">Optional monitor for loading operation.</param> /// <returns>A <see cref="IStObjMap"/> that provides access to the objects graph.</returns> public static IStObjMap?Load(Assembly a, IActivityMonitor?monitor = null) { Throw.CheckNotNullArgument(a); lock ( _alreadyHandled ) { var info = LockedGetMapInfo(a, ref monitor); if (info == null) { return(null); } var alc = AssemblyLoadContext.GetLoadContext(a); if (alc == null) { monitor.Warn($"Assembly '{a.FullName}' is not in any AssemblyLoadContext."); } else if (alc != AssemblyLoadContext.Default) { monitor.Warn($"Assembly '{a.FullName}' is loaded in non-default AssemblyLoadContext '{alc.Name}'."); } return(LockedGetStObjMapFromInfo(info, ref monitor)); } }
void StObjConstruct(IActivityMonitor monitor, IActivityMonitor?anotherLogger = null) { monitor.Should().NotBeNull("This is the Setup monitor."); anotherLogger.Should().BeSameAs(monitor, "All IActivityMonitor are Setup monitors."); monitor.Trace("Setup monitor can be used by StObjConstruct method."); }
public static async ValueTask <Task> PublishAsync(this IMqtt5Client client, IActivityMonitor?m, string topic, QualityOfService qos, bool retain, ReadOnlyMemory <byte> payload, string?responseTopic = null, ushort correlationDataSize = 0, SpanAction?correlationDataWriter = null) //properties => await client.SendPacket <object>(m, new SmallOutgoingApplicationMessage( topic, qos, retain, payload, responseTopic, correlationDataSize, correlationDataWriter ));
public static async ValueTask <Task> PublishAsync(this IMqtt3Client client, IActivityMonitor?m, string topic, QualityOfService qos, bool retain, ReadOnlyMemory <byte> payload) => await client.SendPacket <object>(m, new SmallOutgoingApplicationMessage( topic, qos, retain, payload ));
/// <inheritdoc/> public async Task <ConnectResult> ConnectAsync(IActivityMonitor?m, MqttClientCredentials?credentials = null, OutgoingLastWill?lastWill = null) { if (IsConnected) { throw new InvalidOperationException("This client is already connected."); } using (m?.OpenTrace("Connecting...")) { try { (IOutgoingPacketStore store, IIncomingPacketStore packetIdStore) = await _config.StoreFactory.CreateAsync(m, _pConfig, _config, _config.ConnectionString, credentials?.CleanSession ?? true); IMqttChannel channel = await _config.ChannelFactory.CreateAsync(m, _config.ConnectionString); ConnectAckReflex connectAckReflex = new(); Task <ConnectResult> connectedTask = connectAckReflex.Task; var output = new OutputPump(this, _pConfig); OutputProcessor outputProcessor; var input = new InputPump(this, channel.DuplexPipe.Input, connectAckReflex.ProcessIncomingPacket); OpenPumps(m, new ClientState(input, output, channel, packetIdStore, store)); ReflexMiddlewareBuilder builder = new ReflexMiddlewareBuilder() .UseMiddleware(new PublishReflex(_config, packetIdStore, OnMessage, output)) .UseMiddleware(new PublishLifecycleReflex(packetIdStore, store, output)) .UseMiddleware(new SubackReflex(store)) .UseMiddleware(new UnsubackReflex(store)); if (_config.KeepAliveSeconds == 0) { outputProcessor = new OutputProcessor(output, channel.DuplexPipe.Output, store); } else { OutputProcessorWithKeepAlive withKeepAlive = new(_config, output, channel.DuplexPipe.Output, store);; outputProcessor = withKeepAlive; _ = builder.UseMiddleware(withKeepAlive); } output.StartPumping(outputProcessor); connectAckReflex.Reflex = builder.Build(InvalidPacket); OutgoingConnect outgoingConnect = new(_pConfig, _config, credentials, lastWill); CancellationTokenSource cts = new(_config.WaitTimeoutMilliseconds); IOutgoingPacket.WriteResult writeConnectResult = await outgoingConnect.WriteAsync(_pConfig.ProtocolLevel, channel.DuplexPipe.Output, cts.Token); if (writeConnectResult != IOutgoingPacket.WriteResult.Written) { _ = await CloseAsync(DisconnectedReason.None); return(new ConnectResult(ConnectError.Timeout)); } Task timeout = _config.DelayHandler.Delay(_config.WaitTimeoutMilliseconds, CloseToken); _ = await Task.WhenAny(connectedTask, timeout); // This following code wouldn't be better with a sort of ... switch/pattern matching ? if (connectedTask.Exception is not null) { m?.Fatal(connectedTask.Exception); _ = await CloseAsync(DisconnectedReason.None); return(new ConnectResult(ConnectError.InternalException)); } if (CloseToken.IsCancellationRequested) { _ = await CloseAsync(DisconnectedReason.None); return(new ConnectResult(ConnectError.RemoteDisconnected)); } if (!connectedTask.IsCompleted) { _ = await CloseAsync(DisconnectedReason.None); return(new ConnectResult(ConnectError.Timeout)); } ConnectResult res = await connectedTask; if (res.ConnectError != ConnectError.Ok) { _ = await CloseAsync(DisconnectedReason.None); return(new ConnectResult(res.ConnectError)); } bool askedCleanSession = credentials?.CleanSession ?? true; if (askedCleanSession && res.SessionState != SessionState.CleanSession) { _ = await CloseAsync(DisconnectedReason.None); return(new ConnectResult(ConnectError.ProtocolError_SessionNotFlushed)); } if (res.SessionState == SessionState.CleanSession) { ValueTask task = packetIdStore.ResetAsync(); await store.ResetAsync(); await task; } else { throw new NotImplementedException(); } return(res); } catch (Exception e) { m?.Error("Error while connecting, closing client.", e); _ = await CloseAsync(DisconnectedReason.None); return(new ConnectResult(ConnectError.InternalException)); } } }