Example #1
0
        private void WaitForConfirm(IMessage requestMessage, CorrelationData correlationData)
        {
            try
            {
                if (!correlationData.Future.Wait(WaitForConfirmTimeout))
                {
                    throw new MessageTimeoutException(requestMessage, this + ": Timed out awaiting publisher confirm");
                }

                var confirm = correlationData.Future.Result;
                if (!confirm.Ack)
                {
                    throw new RabbitException("Negative publisher confirm received: " + confirm);
                }

                if (correlationData.ReturnedMessage != null)
                {
                    throw new RabbitException("Message was returned by the broker");
                }
            }
            catch (Exception e)
            {
                throw new RabbitException("Failed to get publisher confirm", e);
            }
        }
            public IMessage PostProcessMessage(IMessage message, CorrelationData correlation)
            {
                var accessor = RabbitHeaderAccessor.GetMutableAccessor(message);

                accessor.RemoveHeaders("__TypeId__");
                return(message);
            }
Example #3
0
        public void DefaultTimedScopeResult_LogsAsSystemError()
        {
            LoggedEvents.Clear();

            CorrelationData data = new CorrelationData();

            UnitTestTimedScopeLogger        unitTestTimedScopeLogger   = new UnitTestTimedScopeLogger();
            Mock <IReplayEventConfigurator> replyEventConfiguratorMock = new Mock <IReplayEventConfigurator>();
            Mock <ICallContextManager>      callContextManagerMock     = new Mock <ICallContextManager>();

            IMachineInformation     machineInformation     = new UnitTestMachineInformation();
            ITimedScopeStackManager timedScopeStackManager = new TimedScopeStackManager(callContextManagerMock.Object, machineInformation);

            using (TimedScope.Create(data, machineInformation, TestHooks.DefaultTimedScopeName, "description", unitTestTimedScopeLogger,
                                     replyEventConfiguratorMock.Object, timedScopeStackManager, default(TimedScopeResult)))
            {
            }

            TimedScopeLogEvent evt = unitTestTimedScopeLogger.SingleTimedScopeEvent(TestHooks.DefaultTimedScopeName);

            if (VerifyNotNullAndReturn(evt, "A scope event has been logged"))
            {
                Assert.Equal(TimedScopeResult.SystemError, evt.Result);
            }
        }
Example #4
0
        /// <summary>
        /// Configure event replaying when a timed scope ends
        /// </summary>
        /// <param name="scope"></param>
        public void ConfigureReplayEventsOnScopeEnd(TimedScope scope)
        {
            CorrelationData currentCorrelation = Correlation.CurrentCorrelation;

            if (scope.IsSuccessful ?? false)
            {
                // assumption is that if any lower level scopes fail that should bubble up to the parent scope; if replay is enabled a previous scope has failed so
                // log some telemetry to help us understand these mixed scenarios better / identify error handling bugs
                if (currentCorrelation != null && currentCorrelation.ShouldReplayUls)
                {
                    // ASSERTTAG_IGNORE_START
                    ULSLogging.LogTraceTag(0, Categories.TimingGeneral, Levels.Warning,
                                           "Scope '{0}' succeeded even though a previous scope on this correlation failed.", scope.Name);
                    // ASSERTTAG_IGNORE_FINISH
                }
            }
            else
            {
                // flip the replay switch on Scope failure for scenarios where its useful to get a verbose ULS trace in production
                if (currentCorrelation != null &&
                    scope.Result.ShouldReplayEvents() &&
                    !scope.IsTransaction &&
                    !scope.ScopeDefinition.OnDemand &&
                    !scope.DisableVerboseUlsCapture &&
                    !DisabledTimedScopes.IsDisabled(scope.ScopeDefinition))
                {
                    currentCorrelation.ShouldReplayUls = true;
                    currentCorrelation.ReplayPreviouslyCachedUlsEvents();
                }
            }
        }
Example #5
0
        public void SuccessTimedScope_DoesntReplayLogs()
        {
            Mock <ITimedScopeLogger>        timedScopeLoggerMock       = new Mock <ITimedScopeLogger>();
            Mock <IReplayEventConfigurator> replyEventConfiguratorMock = new Mock <IReplayEventConfigurator>();

            Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
            Correlation.CorrelationStart(new CorrelationData());

            IMachineInformation     machineInformation     = new UnitTestMachineInformation();
            ITimedScopeStackManager timedScopeStackManager = new TimedScopeStackManager(CallContextManagerInstance, machineInformation);
            CorrelationData         currentCorrelation     = Correlation.CurrentCorrelation;

            Assert.False(currentCorrelation.ShouldReplayUls);

            using (TimedScope scope = TimedScope.Start(currentCorrelation, MachineInformation, "TestScope", customLogger: timedScopeLoggerMock.Object,
                                                       replayEventConfigurator: replyEventConfiguratorMock.Object, timedScopeStackManager: timedScopeStackManager))
            {
                scope.Result = TimedScopeResult.Success;

                Mock <IReplayEventDisabledTimedScopes> disabledScopes = new Mock <IReplayEventDisabledTimedScopes>();
                disabledScopes.Setup(x => x.IsDisabled(scope.ScopeDefinition)).Returns(false);

                ReplayEventConfigurator configurator = new ReplayEventConfigurator(disabledScopes.Object, Correlation);
                configurator.ConfigureReplayEventsOnScopeEnd(scope);
            }

            Assert.False(currentCorrelation.ShouldReplayUls);
        }
		public void FailedTimedScope_ShouldReplayLogs()
		{

			Mock<ITimedScopeLogger> timedScopeLoggerMock = new Mock<ITimedScopeLogger>();
			Mock<IReplayEventConfigurator> replyEventConfiguratorMock = new Mock<IReplayEventConfigurator>();
			Mock<ILogEventCache> mockCache = new Mock<ILogEventCache>();

			Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
			Correlation.CorrelationStart(new CorrelationData(mockCache.Object));

			IMachineInformation machineInformation = new UnitTestMachineInformation();
			ITimedScopeStackManager timedScopeStackManager = new TimedScopeStackManager(CallContextManagerInstance, machineInformation);
			CorrelationData currentCorrelation = Correlation.CurrentCorrelation;

			Assert.False(currentCorrelation.ShouldReplayUls, "Logs shouldn't be replayed");

			using (TimedScope scope = TestHooks.CreateDefaultTimedScope(
				timedScopeLoggerMock.Object,
				replyEventConfiguratorMock.Object,
				machineInformation,
				timedScopeStackManager,
				startScope: true))
			{
				scope.Result = TimedScopeResult.SystemError;

				Mock<IReplayEventDisabledTimedScopes> disabledScopes = new Mock<IReplayEventDisabledTimedScopes>();
				disabledScopes.Setup(x => x.IsDisabled(scope.ScopeDefinition)).Returns(false);

				ReplayEventConfigurator configurator = new ReplayEventConfigurator(disabledScopes.Object, Correlation);
				configurator.ConfigureReplayEventsOnScopeEnd(scope);
			}

			Assert.True(currentCorrelation.ShouldReplayUls, "Logs should be replayed");
		}
Example #7
0
        public IMessage PostProcessMessage(IMessage message, CorrelationData correlation)
        {
            var accessor = RabbitHeaderAccessor.GetMutableAccessor(message);

            accessor.Delay = 15000;
            return(message);
        }
Example #8
0
        protected virtual CorrelationData GenerateCorrelationData(IMessage requestMessage)
        {
            CorrelationData correlationData = null;

            if (CorrelationDataGenerator != null)
            {
                var messageId = requestMessage.Headers.Id;
                if (messageId == null)
                {
                    messageId = _no_id;
                }

                var userData = CorrelationDataGenerator.ProcessMessage(requestMessage);
                if (userData != null)
                {
                    correlationData = new CorrelationDataWrapper(messageId, userData, requestMessage);
                }
                else
                {
                    _logger?.LogDebug("'confirmCorrelationExpression' resolved to 'null'; no publisher confirm will be sent to the ack or nack channel");
                }
            }

            return(correlationData);
        }
Example #9
0
        protected void HandleConfirm(CorrelationData correlationData, bool ack, string cause)
        {
            var wrapper = (CorrelationDataWrapper)correlationData;

            if (correlationData == null)
            {
                _logger.LogDebug("No correlation data provided for ack: " + ack + " cause:" + cause);
                return;
            }

            var      userCorrelationData = wrapper.UserData;
            IMessage confirmMessage;

            confirmMessage = BuildConfirmMessage(ack, cause, wrapper, userCorrelationData);
            if (ack && GetConfirmAckChannel() != null)
            {
                SendOutput(confirmMessage, GetConfirmAckChannel(), true);
            }
            else if (!ack && GetConfirmNackChannel() != null)
            {
                SendOutput(confirmMessage, GetConfirmNackChannel(), true);
            }
            else
            {
                _logger.LogInformation("Nowhere to send publisher confirm " + (ack ? "ack" : "nack") + " for " + userCorrelationData);
            }
        }
        /// <summary>
        /// Logs the scope end
        /// </summary>
        /// <param name="scope">Scope to log</param>
        /// <param name="data">Correlation data</param>
        public void LogScopeEnd(TimedScope scope, CorrelationData data)
        {
            if (!Code.ValidateArgument(scope, nameof(scope), TaggingUtilities.ReserveTag(0x2375d3d8 /* tag_933py */)) ||
                !Code.ValidateArgument(data, nameof(data), TaggingUtilities.ReserveTag(0x2375d3d9 /* tag_933pz */)))
            {
                return;
            }

            if (scope.IsTransaction)
            {
                m_eventSource.LogEvent(Categories.TimingGeneral,
                                       name: scope.Name,
                                       subtype: scope.SubType ?? NullPlaceholder,
                                       metadata: scope.MetaData ?? NullPlaceholder,
                                       serviceName: ServiceName ?? NullPlaceholder,
                                       result: scope.Result,
                                       correlationId: data.VisibleId.ToString("D", CultureInfo.InvariantCulture),
                                       durationMs: scope.DurationInMilliseconds);
            }
            else
            {
                m_eventSource.LogEvent(Categories.TimingGeneral,
                                       name: scope.Name,
                                       subtype: scope.SubType ?? NullPlaceholder,
                                       metadata: scope.MetaData ?? NullPlaceholder,
                                       serviceName: ServiceName ?? NullPlaceholder,
                                       userHash: data.Data(TimedScopeDataKeys.InternalOnly.UserHash) ?? data.UserHash ?? NullPlaceholder,
                                       result: scope.Result,
                                       correlationId: data.VisibleId.ToString("D", CultureInfo.InvariantCulture),
                                       durationMs: scope.DurationInMilliseconds);
            }
        }
Example #11
0
        public void CorrelationClear_ShouldClearAllCorrelations()
        {
            try
            {
                Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
                Correlation.CorrelationStart(null);

                CorrelationData previousCorrelation = Correlation.CurrentCorrelation;
                for (int i = 0; i < (new Random()).Next(3, 10); i++)
                {
                    Correlation.CorrelationStart(null);
                    CorrelationData parentCorrelation = Correlation.CurrentCorrelation.ParentCorrelation;
                    Assert.Same(previousCorrelation, parentCorrelation);
                    previousCorrelation = Correlation.CurrentCorrelation;
                }

                Assert.NotNull(Correlation.CurrentCorrelation);

                Correlation.CorrelationClear();

                Assert.Null(Correlation.CurrentCorrelation);
            }
            finally
            {
                EndRequest();
            }
        }
Example #12
0
        public void CorrelationData_SetParentCausingCircularReference_ShouldThrow()
        {
            CorrelationData data = new CorrelationData();

            data.ParentCorrelation = new CorrelationData();
            Assert.Throws <InvalidOperationException>(
                () => data.ParentCorrelation.ParentCorrelation = data);
        }
Example #13
0
            public IMessage PostProcessMessage(IMessage message, CorrelationData correlation)
            {
                var accessor = RabbitHeaderAccessor.GetMutableAccessor(message);

                accessor.ContentType = "application/json";
                accessor.UserId      = "guest";
                accessor.SetHeader("stringHeader", "string");
                accessor.SetHeader("intHeader", 42);
                return(message);
            }
Example #14
0
        public void CorrelationData_SetParent_ShouldThrowIfAlreadySet()
        {
            CorrelationData data = new CorrelationData();

            data.ParentCorrelation = new CorrelationData();
            Assert.True(true, "Should not throw when the parent correlation is not set");

            Assert.Throws <InvalidOperationException>(
                () => data.ParentCorrelation = new CorrelationData());
        }
Example #15
0
        /// <summary>
        /// The id of the running transaction
        /// </summary>
        /// <param name="correlation">current correlation</param>
        /// <returns>id of the running transaction</returns>
        public static uint RunningTransaction(CorrelationData correlation)
        {
            uint id = Transactions.None;

            if (correlation != null)
            {
                id = correlation.TransactionId;
            }

            return(id);
        }
Example #16
0
        /// <summary>
        /// Creates an instance of the test counters timed scope.
        /// </summary>
        /// <param name="initialResult">Initial scope result.</param>
        /// <param name="startScope">Start scope implicitly</param>
        /// <param name="machineInformation">Machine information</param>
        /// <param name="scopeLogger">Custom logger</param>
        /// <param name="replayEventConfigurator">Replay event configurator</param>
        /// <param name="timedScopeStackManager">Timed scope stack manager</param>
        /// <returns>The created scope.</returns>
        public static TimedScope CreateTestCountersUnitTestTimedScope(
            bool?initialResult = null,
            bool startScope    = true,
            IMachineInformation machineInformation           = null,
            ITimedScopeLogger scopeLogger                    = null,
            IReplayEventConfigurator replayEventConfigurator = null,
            ITimedScopeStackManager timedScopeStackManager   = null)
        {
            CorrelationData data = new CorrelationData();

            return(UnitTestTimedScopes.TestCounters.UnitTest.Create(data, machineInformation, scopeLogger, replayEventConfigurator, timedScopeStackManager, initialResult, startScope));
        }
Example #17
0
        /// <summary>
        /// Logs the scope end
        /// </summary>
        /// <param name="scope">Scope to log</param>
        /// <param name="data">Correlation data</param>
        public void LogScopeEnd(TimedScope scope, CorrelationData data)
        {
            if (scope == null)
            {
                return;
            }

            TimedScopeLogEvent evt = new TimedScopeLogEvent(scope.Name, scope.SubType,
                                                            scope.MetaData, scope.Result, scope.FailureDescription,
                                                            data.Data(TimedScopeDataKeys.InternalOnly.UserHash),
                                                            scope.Duration ?? TimeSpan.Zero);

            m_events.Enqueue(evt);
        }
Example #18
0
        public void CorrelationData_Clone_ShouldReturnCopyOfCorrelationData()
        {
            CorrelationData data = new CorrelationData();

            data.ParentCorrelation = new CorrelationData();
            data.AddData(CorrelationData.TransactionIdKey, "1");
            data.ShouldLogDirectly = true;

            CorrelationData clone = data.Clone();

            Assert.NotSame(data, clone);
            Assert.Equal(data.VisibleId, clone.VisibleId);
            Assert.Equal(data.ShouldLogDirectly, clone.ShouldLogDirectly);
            Assert.Equal(data.HasData, clone.HasData);
        }
Example #19
0
        private void Simulation(object arg)
        {
            SentSignal.Clear();
            ReceivedSignal.Clear();
            var samplingPeriod = 1.0d / SentSignalData.SamplingFrequency.Value;

            for (var i = 0; i < SentSignalData.NumberOfSamples.Value; i++)
            {
                SentSignal.Add(new ObservableValue(Signal(i * samplingPeriod)));
                ReceivedSignal.Add(new ObservableValue(Signal(i * samplingPeriod)));
            }

            CorrelationData.AddRange(Correlation
                                     .Correlate(SentSignal.Select(p => new Point(0, p.Value)),
                                                ReceivedSignal.Select(p => new Point(0, p.Value))).Select(p => new ObservableValue(p.Y)));

            simulationThread = new Thread(RunSimulation);
            simulationThread.Start();
        }
Example #20
0
        public void Scope_TimedScopeLogger_IsCalled()
        {
            Mock <ITimedScopeLogger>        timedScopeLoggerMock       = new Mock <ITimedScopeLogger>();
            Mock <IReplayEventConfigurator> replyEventConfiguratorMock = new Mock <IReplayEventConfigurator>();
            Mock <ICallContextManager>      callContextManagerMock     = new Mock <ICallContextManager>();

            IMachineInformation     machineInformation     = new UnitTestMachineInformation();
            ITimedScopeStackManager timedScopeStackManager = new TimedScopeStackManager(callContextManagerMock.Object, machineInformation);

            TimedScope      scope;
            CorrelationData data = new CorrelationData();

            using (scope = TestHooks.CreateTimedScopeProvider(machineInformation, timedScopeLoggerMock.Object, replyEventConfiguratorMock.Object, timedScopeStackManager)
                           .Create(new TimedScopeDefinition("TestScope"), TimedScopeResult.SystemError))
            {
                timedScopeLoggerMock.Verify(x => x.LogScopeStart(scope), Times.Once);
                timedScopeLoggerMock.Verify(x => x.LogScopeEnd(scope, It.IsAny <CorrelationData>()), Times.Never);
            }

            timedScopeLoggerMock.Verify(x => x.LogScopeEnd(scope, It.IsAny <CorrelationData>()), Times.Once);
        }
Example #21
0
        public void CorrelationData_ToTransactionData_ShouldReturnSameData()
        {
            try
            {
                Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);
                Correlation.CorrelationStart(null);

                CorrelationData data        = Correlation.CurrentCorrelation;
                TransactionData transaction = data.ToTransactionData();

                Assert.True(data.CallDepth == transaction.CallDepth, "Call depth properties should be equal.");
                Assert.True(data.EventSequenceNumber == transaction.EventSequenceNumber, "EventSequenceNumber properties should be equal.");
                Assert.True(data.TransactionId == transaction.TransactionId, "TransactionId properties should be equal.");
                Assert.True(data.UserHash == transaction.UserHash, "UserHash properties should be equal.");
                Assert.True(data.VisibleId == transaction.CorrelationId, "VisibleId and CorrelationId properties should be equal.");
            }
            finally
            {
                EndRequest();
            }
        }
Example #22
0
        public void CorrelationStart_MultipleCorrelations_ShouldCreateAHierarchy()
        {
            Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);

            for (int i = 0; i < 3; i++)
            {
                Correlation.CorrelationStart(null);
                CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
                for (int j = i; j > 0; j--)
                {
                    CorrelationData parentCorrelation = currentCorrelation.ParentCorrelation;

                    Assert.NotNull(parentCorrelation);
                    Assert.NotEqual(currentCorrelation.VisibleId, parentCorrelation.VisibleId);

                    currentCorrelation = parentCorrelation;
                }
                Assert.Null(currentCorrelation.ParentCorrelation);
            }

            CallContextManagerInstance.CallContextOverride.EndCallContext();
        }
Example #23
0
        public void CorrelationEnd_MultipleCorrelations_ShouldUnwindHierarchy()
        {
            Correlation = new Correlation(new MemoryCorrelationHandler(), CallContextManagerInstance, MachineInformation);

            Guid[] correlations = new Guid[3];
            for (int i = 0; i < 3; i++)
            {
                Correlation.CorrelationStart(null);
                correlations[i] = Correlation.CurrentCorrelation.VisibleId;
            }

            for (int i = 2; i >= 0; i--)
            {
                CorrelationData currentCorrelation = Correlation.CurrentCorrelation;
                Assert.Equal(correlations[i], currentCorrelation.VisibleId);

                Correlation.CorrelationEnd();
            }

            Assert.Null(Correlation.CurrentCorrelation);

            CallContextManagerInstance.CallContextOverride.EndCallContext();
        }
Example #24
0
        protected virtual CorrelationData GenerateCorrelationData(IMessage requestMessage)
        {
            CorrelationData correlationData = null;

            var messageId = requestMessage.Headers.Id ?? _no_id;

            if (CorrelationDataGenerator != null)
            {
                var userData = CorrelationDataGenerator.ProcessMessage(requestMessage);

                if (userData != null)
                {
                    correlationData = new CorrelationDataWrapper(messageId, userData, requestMessage);
                }
                else
                {
                    _logger?.LogDebug("'confirmCorrelationExpression' resolved to 'null'; no publisher confirm will be sent to the ack or nack channel");
                }
            }

            if (correlationData == null)
            {
                object correlation = requestMessage.Headers[RabbitMessageHeaders.PUBLISH_CONFIRM_CORRELATION];

                if (correlation is CorrelationData cdata)
                {
                    correlationData = cdata;
                }

                if (correlationData != null)
                {
                    correlationData = new CorrelationDataWrapper(messageId, correlationData, requestMessage);
                }
            }

            return(correlationData);
        }
Example #25
0
        /// <summary>
        /// Construct the event arguments
        /// </summary>
        /// <param name="correlation">correlation data</param>
        /// <param name="shouldLogDirectly">should log directly to underlying log handler</param>
        /// <param name="tagId">tag id</param>
        /// <param name="categoryId">category id</param>
        /// <param name="traceLevel">trace level</param>
        /// <param name="message">message</param>
        /// <param name="details">additional details to include in message which is not part of message formatting and parameters</param>
        /// <param name="parameters">parameters for the message</param>
        public LogEventArgs(CorrelationData correlation, bool shouldLogDirectly, uint tagId, Category categoryId, Level traceLevel, string message, string details, params object[] parameters)
        {
            TagId             = tagId;
            CategoryId        = categoryId;
            ThreadId          = Thread.CurrentThread.ManagedThreadId;
            Details           = details;
            Level             = traceLevel;
            Message           = message;
            MessageParameters = parameters;
            ServerTimestamp   = Stopwatch.GetTimestamp();
            ServerTimeUtc     = DateTime.UtcNow;

            CorrelationData = correlation;
            if (CorrelationData != null)
            {
                ShouldLogDirectly = CorrelationData.ShouldLogDirectly;
                SequenceNumber    = CorrelationData.NextEventSequenceNumber();
            }
            else
            {
                ShouldLogDirectly = shouldLogDirectly;
                SequenceNumber    = -1;
            }
        }
 public IMessage PostProcessMessage(IMessage message, CorrelationData correlation)
 {
     return(null);
 }
 /// <summary>Initializes a new instance of the <see cref="PendingConfirm"/> class.</summary>
 /// <param name="correlationData">The correlation data.</param>
 /// <param name="timestamp">The timestamp.</param>
 public PendingConfirm(CorrelationData correlationData, long timestamp)
 {
     this.correlationData = correlationData;
     this.timestamp = timestamp;
 }
        public override void Send(string exchange, string routingKey, IMessage message, CorrelationData correlationData)
        {
            lock (_batchlock)
            {
                _count++;
                if (correlationData != null)
                {
                    _logger?.LogDebug("Cannot use batching with correlation data");
                    base.Send(exchange, routingKey, message, correlationData);
                }
                else
                {
                    // if (_scheduledTask != null)
                    // {
                    //    _cancellationTokenSource.Cancel(false);
                    // }
                    MessageBatch?batch = _batchingStrategy.AddToBatch(exchange, routingKey, message);
                    if (batch != null)
                    {
                        if (_scheduledTask != null)
                        {
                            _cancellationTokenSource.Cancel(false);
                            _scheduledTask = null;
                        }

                        base.Send(batch.Value.Exchange, batch.Value.RoutingKey, batch.Value.Message, null);
                    }

                    var next = _batchingStrategy.NextRelease();
                    if (next != null && _scheduledTask == null)
                    {
                        _cancellationTokenSource = new CancellationTokenSource();
                        var delay = next.Value - DateTime.Now;
                        _scheduledTask = Task.Run(() => ReleaseBatches(delay, _cancellationTokenSource.Token), _cancellationTokenSource.Token);
                    }
                }
            }
        }
Example #29
0
 public IMessage PostProcessMessage(IMessage message, CorrelationData correlation)
 {
     Headers.Add(message.Headers.Get <string>("bean"));
     Headers.Add(message.Headers.Get <string>("method"));
     return(message);
 }
Example #30
0
        public void CorrelationData_CloneWithNullData_ShouldReturnNull()
        {
            CorrelationData data = null;

            Assert.Null(data.Clone());
        }
Example #31
0
        public void CorrelationData_NullObjectToTransactionData_ShouldReturnNull()
        {
            CorrelationData data = null;

            Assert.Null(data.ToTransactionData());
        }