Search code examples
c#multithreadingasp.net-web-apinewrelic

How to invoke code on same thread the request started


I have a POST controller method using async await in a lot of services and in the controller level I need to send some New Relic parameters. New Relic gives warning logs when parameters are sent from a thread other than the requests started.

NewRelic WARN: Agent API Error: An error occurred invoking API method "AddCustomParameter" - "System.InvalidOperationException: The API method called is only valid from within a transaction. This error can occur if you call the API method from a thread other than the one the transaction started on. at NewRelic.Agent.Core.Api.AsyncAgentApi.GetCurrentTransactionBuilder()
at NewRelic.Agent.Core.Api.AsyncAgentApi.AddCustomParameter(String key, String value)"

How can I invoke the code that send the parameter value to New Relic in my controller method?

Example, the below code in the controller.

var threadid = Thread.CurrentThread.ManagedThreadId;
Log.Debug($"Before async method : {ThreadIdMessage(threadid)}");
var reportObject = await ReportService.GetReportAsync(requestModel).ConfigureAwait(true);
if (reportObject.PolicyModels != null)
{
    threadid = Thread.CurrentThread.ManagedThreadId;
    Log.Debug($"Before sending New Relic values: {ThreadIdMessage(threadid)}");
    AddPoliciesCountInNewRelic(reportObject.PolicyModels.Count);
    AddTotalTransactionsCountInNewRelic(
                            reportObject.PolicyModels.SelectMany(p => p.PolicyTransactionModels).Count());
    threadid = Thread.CurrentThread.ManagedThreadId;
    Log.Debug($"After sending New Relic values: {ThreadIdMessage(threadid)}");
}

Will print

DEBUG - Before async method : Current Thread Id: 5  
DEBUG - Before sending New Relic values: Current Thread Id: 9
NewRelic.AddCustomParameter(CVPoliciesCount,2)
NewRelic.AddCustomParameter(CVTotalTransactionsCount,8)  
DEBUG - After sending New Relic values: Current Thread Id: 9

Per New Relic warning log I should invoke the AddCustomParameter method in thread id 5.

AddPoliciesCountInNewRelic and AddTotalTransactionsCountInNewRelic calls the ApiControllerBase.AddNewRelicParameter(string, string) base class protected method.

private void AddPoliciesCountInNewRelic(int policiesCount)
{
    AddNewRelicParameter("CVPoliciesCount", policiesCount.ToString());
}

private void AddTotalTransactionsCountInNewRelic(int transactionsCount)
{
    AddNewRelicParameter("CVTotalTransactionsCount", transactionsCount.ToString());
}

protected void AddNewRelicParameter(string key, string value)
{
    if (!string.IsNullOrWhiteSpace(key) &&
        !string.IsNullOrWhiteSpace(value))
    {
        try
        {
            NewRelic.Api.Agent.NewRelic.AddCustomParameter(key, value);
        }
        catch (Exception ex)
        {
            Log.Error($"ERROR! : New Relic Parameter Exception {ex}");
        }
    }
}

Solution

  • I was able to work around the problem with using continuation but like @evk commented in @Tim answer, New Relic should account to fix this so that we don't have to workaround proper code to meet such requirement.

        var context = TaskScheduler.FromCurrentSynchronizationContext();    
        var threadid = Thread.CurrentThread.ManagedThreadId;
        Log.Debug($"Entry ThreadID: {threadid}");
        var getReportTask = ReportService.GetReportAsync(requestModel);
        getReportTask.ContinueWith(antecedent =>
        {
            var continuationThreadid = Thread.CurrentThread.ManagedThreadId;
            Log.Debug($"continuationThreadid: {continuationThreadid}");
            var result = antecedent.Result;
            if (result.PolicyModels != null)
            {
                AddPoliciesCountInNewRelic(result.PolicyModels.Count);
                AddTotalTransactionsCountInNewRelic(
                    result.PolicyModels.SelectMany(p => p.PolicyTransactionModels).Count());
            }
        }, context);
    
        var reportObject = await getReportTask.ConfigureAwait(false);
    

    It will print as expected the same thread id.

    DEBUG - Entry ThreadID: 5 
    DEBUG - continuationThreadid: 5