Azure / azure-functions-dotnet-worker

Azure Functions out-of-process .NET language worker
MIT License
425 stars 182 forks source link

IMessageReceiver on ServiceBusTrigger #293

Closed MattJeanes closed 1 year ago

MattJeanes commented 3 years ago

I understand IMessageReceiver is no longer bindable with dotnet-isolated but what is the recommended way to achieve the functions it previously provided now? For example, manual completion of the message, receiving deferred messages, deferring messages etc. I cannot see any way to do this now.

fabiocav commented 3 years ago

Those scenarios are not currently supported. We'll continue to close those gaps as we enhance the binding model in the host and adopt that in the worker, but in the meantime, to use bindings for those scenarios, you still need to use the in-proc model.

MattJeanes commented 3 years ago

That is a shame, but thank you for the quick response. Is this planned for .NET 5 or is this more like something on the roadmap for .NET 6? Just would like to get an idea of a timeline as this is blocking our upgrade to .NET 5

timgabrhel commented 3 years ago

Another vote here. Our .NET 5 upgrade was dependent on the new isolated hosting model, but I'm now stuck for this same reason. We have two functions that are dependent on being able to complete/abandon + update message Properties on messages.

fabiocav commented 3 years ago

To provide an update and answer the previous question, this requires enhancements to the host and SDK so the Worker can leverage the new capabilities once introduced, and given the timelines, this will land in-proc in .NET 6 prior to being done in the worker.

A lot of activity will be happening in the coming weeks to make a .NET 6 preview available in a new major version of Functions.

MatheusXavier commented 3 years ago

I have a service where I can complete the messages, so I don't need the IMessegaReceiver, but I need the lockToken, Are there some way to get that? On documentation they talk about the MessageReceiver so I used this property with string type but I received a json with empty TokenProvider:

  "RegisteredPlugins": [

  ],
  "ReceiveMode": 0,
  "PrefetchCount": 100,
  "LastPeekedSequenceNumber": 0,
  "Path": "desk4me-monitoring-statuses-local-development",
  "OperationTimeout": "00:01:00",
  "ServiceBusConnection": {
    "Endpoint": "sb://desk4me-dev.servicebus.windows.net",
    "OperationTimeout": "00:01:00",
    "RetryPolicy": {
      "MinimalBackoff": "00:00:00",
      "MaximumBackoff": "00:00:30",
      "DeltaBackoff": "00:00:03",
      "MaxRetryCount": 5,
      "IsServerBusy": false,
      "ServerBusyExceptionMessage": null
    },
    "TransportType": 0,
    "TokenProvider": {

    },
    "IsClosedOrClosing": false
  },
  "IsClosedOrClosing": false,
  "OwnsConnection": true,
  "ClientId": "MessageReceiver1desk4me-monitoring-statuses-local-development",
  "RetryPolicy": {
    "MinimalBackoff": "00:00:00",
    "MaximumBackoff": "00:00:30",
    "DeltaBackoff": "00:00:03",
    "MaxRetryCount": 5,
    "IsServerBusy": false,
    "ServerBusyExceptionMessage": null
  }
}
timgabrhel commented 3 years ago

@MatheusXavier The lockToken can be found in the binding metadata off of the FunctionContext passed into the function. I tried to init my own SB connection and mark the message as complete with the lock token, but service bus rejects it as the message was received by another MessageReceiver. Curious if you have an alternative to make it work.

MatheusXavier commented 3 years ago

@timgabrhel I tried your solution, so I could get lockToken from BindingContext: var lockToken = context.BindingContext.BindingData.GetValueOrDefault("LockToken").ToString(); I discovered that if you add a parameter on your function called string lockToken you could get it too, but when I tried to complete the message I got the same error:

{"The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue, or was received by a different receiver instance."}

I built the Microsoft.Azure.ServiceBus.QueueClient with all my Service Bus and Queue connection settings and then called the CompleteAsync but it didn't work. If I get this QueueClient and register a message handler, and inside it I use the CompleteAsync It works fine.

timgabrhel commented 3 years ago

@MatheusXavier That's great to hear! Any chance you can share a code snippet around the message handler & completing the message?

MatheusXavier commented 3 years ago

Sure! I use two differents queues on my application, the first one I use to handle events from my domain, on application startup I register a message handler that receives events, it is working perfectly, this is the code:

        public void StartConsumer()
        {
            _serviceBusConnection.GetQueue().RegisterMessageHandler(
                async (message, token) =>
                {
                    var messageData = Encoding.UTF8.GetString(message.Body);

                    // Complete the message so that it is not received again.
                    if (await ProcessEventAsync(message.Label, messageData))
                    {
                        await _serviceBusConnection.GetQueue().CompleteAsync(message.SystemProperties.LockToken);
                    }
                },
                new MessageHandlerOptions(ExceptionReceivedHandler)
                {
                    MaxConcurrentCalls = 10,
                    AutoComplete = false,
                });
        }

The second queue I use to process some data, so I use an Azure Function with Service Bus Trigger, this is the code on dotnet 3.1 version:

    public class Processor
    {
        private readonly IMediator _mediator;

        public Processor(IMediator mediator)
        {
            _mediator = mediator;
        }

        [FunctionName("processor")]
        public async Task Run(
            [ServiceBusTrigger("%QueueName%", Connection = "ServiceBusConnection")]
            Message message,
            ILogger log,
            MessageReceiver messageReceiver)
        {
            try
            {
                var jsonData = Encoding.UTF8.GetString(message.Body);
                var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
                var deserialized = JsonConvert.DeserializeObject(jsonData, settings);

                await _mediator.Send(deserialized);
                await messageReceiver.CompleteAsync(message.SystemProperties.LockToken);
            }
            catch (Exception ex)
            {
                log.LogError(ex, $"[Processor] - An error with message: {ex.Message} occurred while " +
                    $"handling message with Id: '{message.MessageId}' and label: '{message.Label}' on {ex.StackTrace}");
            }
        }
    }

After the migration to dotnet 5 we don't have the MessageReceiver anymore, so I tried to use my _serviceBusConnection instance to complete the messages like the first one sample. This is the code:

    public class Processor
    {
        private readonly IMediator _mediator;
        private readonly IServiceBusPersisterConnection _serviceBus;

        public Processor(
            IMediator mediator,
            IServiceBusPersisterConnection serviceBus)
        {
            _mediator = mediator;
            _serviceBus = serviceBus;
        }

        [Function("processor")]
        public async Task Run(
            [ServiceBusTrigger("%QueueName%", Connection = "serviceBusConnection")] string message,
            string messageReceiver,
            string systemProperties,
            string messageId,
            string lockToken,
            FunctionContext context)
        {
            try
            {
                var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
                var deserialized = JsonConvert.DeserializeObject(message, settings);

                await _mediator.Send(deserialized);
                await _serviceBus.GetQueue().CompleteAsync(lockToken);
            }
            catch (Exception ex)
            {
                var log = context.GetLogger<Processor>();

                log.LogError(ex, $"[Processor] - An error with message: {ex.Message} occurred while " +
                    $"handling message with Id: '{messageId}' on {ex.StackTrace}");
            }
        }
    }

But I'm gettins this error on complete:

{"The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue, or was received by a different receiver instance."}

Any ideia to solve that?

timgabrhel commented 3 years ago

@MatheusXavier My apologies, I misunderstood and thought you meant you somehow were able to register a new message handler within the function to complete the message. Looks like we're on hold until this support is enabled by the functions team.

Prinsn commented 3 years ago

Is there any update on this?

This needs to be added to breaking changes, or some kind of guide needs to be written for how to get 3.x functions working with 5.0.

From all the research I have done today, it appears that Azure Functions hard stop an upgrade to .NET Core 5.0.

Am I mistaken in this understanding?

The fact that there is on official response on how to mitigate or migrate away from this is honestly damning.

MattJeanes commented 3 years ago

@Prinsn we had to dual target some of our library projects in the end and hold back upgrading most of our Microsoft.Extensions packages in order to upgrade to .NET 5.0 for our APIs while leaving the functions at .NET Core 3.1 without breaking either. It was a huge pain though, so I'm looking forward to Azure Functions being upgraded properly for .NET 6.

Prinsn commented 3 years ago

@MattJeanes πŸ™ Is there any guide or anything you follow or something you could indicate as to how to manage it? Our project requires the app be 5.0, and we are currently troubleshooting our functions and I cannot find any mention of how to differentially modify the functions to support this.

MattJeanes commented 3 years ago

@Prinsn I didn't follow any guides myself I'm not aware of any. It was pretty much a case of changing target framework to net5.0 for our console apps / APIs but leaving the Azure Functions at netcoreapp3.1 and ensuring any library projects shared between them were netstandard2.1 or dual targetting net5.0 and netcoreapp3.1.

NuGet packages in the library projects especially from Microsoft had to be kept at 3.x such as Microsoft.Extensions.Logging as upgrading them breaks Azure Functions due to the in-process hosting model - which is incidentially one of the biggest reasons to move to out-of-process hosting using dotnet-isolated but it's just simply not feature complete for our needs yet, and a lot of work to change everything to use that as well.

In another fun twist of events though some Microsoft packages have to be upgraded to 5.x in the web projects such as Microsoft.AspNetCore.Mvc.NewtonsoftJson or that will break the APIs πŸ˜„

I wish you the best of luck, make sure to test them well after upgrading!

Prinsn commented 3 years ago

@MattJeanes just to clarify, from the sounds of the Web project needing things, which is technically unrelated to the Azure Function project, you had to keep some things at 3.x if they were merely referenced?

MattJeanes commented 3 years ago

@Prinsn yeah so for Microsoft.AspNetCore.Mvc.NewtonsoftJson for example that was referenced directly by the web project as 5.x which is fine, it's the NuGet packages used in your library projects like Microsoft.Extensions.Logging or Microsoft.Extensions.Configuration or Microsoft.EntityFrameworkCore etc that need to stay at 3.x if those libraries are referenced by your Azure Functions projects as it will break them.

We had an additional objective of ensuring package versions were consistent across the solution which is what also what drove our approach. We didn't want to say use Microsoft.Extensions.Logging@5.0.0 in one library project that wasn't referenced by an Azure Functions but Microsoft.Extensions.Logging@3.1.2 in another one that was, it would become a nightmare real fast.

I'd share the VS solution with you if I could but it's not open source and I'd definitely get into a lot of trouble! πŸ˜„

Below is a PowerShell script I wrote to upgrade all packages to their latest versions across a solution, except for ones that should stay at 3.x. It's not perfect but it might help you or others who take this approach:


$ErrorActionPreference = "Stop"
function Get-SemVer {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Version
    )

    $semverRegex = "^(\d+)\.(\d+)\.(\d+)\.?(\d+)?-?(.+)?"
    if ($Version -match $semverRegex) {
        return @{ Version = $Matches[0]; Major = [int]$Matches[1]; Minor = [int]$Matches[2]; Patch = [int]$Matches[3]; Patch2 = [int]$Matches[4]; PreRelease = $Matches[5] } 
    }
    else {
        Write-Debug "Could not get semver from version '$Version'"
        return $null
    }
}

function Get-NugetLatestPackage {
    param(
        [Parameter(Mandatory = $true)]
        [string]$PackageName,

        [Parameter(Mandatory = $false)]
        [int]$MajorVersion,

        [Parameter(Mandatory = $false)]
        [switch]$IncludePreRelease
    )
    $versions = @()
    $res = Invoke-RestMethod "https://api.nuget.org/v3/registration5-gz-semver2/$($PackageName.ToLower())/index.json"
    $versions += $res.items.items.catalogEntry | Where-Object { $_.listed } | ForEach-Object { $_.version }
    $res.items.'@id' | Where-Object { -not $_.Contains("#") } | ForEach-Object {
        Write-Debug "Calling $_"
        $items = Invoke-RestMethod $_
        $versions += $items.items.catalogEntry | Where-Object { $_.listed } | ForEach-Object { $_.version }
    }
    $selectedVersion = $versions `
    | Where-Object { $_ -and (Get-SemVer $_) }
    | ForEach-Object { Get-SemVer $_ }
    | Sort-Object Major, Minor, Patch, Patch2, PreRelease -Descending `
    | Where-Object { (-not $MajorVersion -or ($MajorVersion -eq $_.Major)) -and (-not $_.PreRelease -or $IncludePreRelease) } `
    | Select-Object -First 1
    return $selectedVersion
}

$currentPackages = @{}
$files = Get-ChildItem -Exclude "(exclude-any-projects-here)" | Get-ChildItem -Include "*.csproj" -Recurse
$files | ForEach-Object {
    $xml = [xml](Get-Content -Raw $_.FullName)
    $xml.Project.ItemGroup.PackageReference | Where-Object { $_.Include -and $_.Version } | ForEach-Object {
        $currentPackages[$_.Include] = Get-SemVer $_.Version
    }
}

$ignorePackages = @{
}

$majorVersionLocks = @{
    "^Microsoft\.Extensions" = 3
    "^Microsoft\.AspNetCore" = 3
    "^Microsoft\.EntityFrameworkCore" = 3
    "^System\.ComponentModel\.Annotations" = 4
    "^Toolbelt" = 3
}

$newPackages = @{}
$currentPackages.Keys | ForEach-Object {
    $package = $_
    if ($ignorePackages[$package]) {
        Write-Warning "Ignoring package $package"
        return
    }
    $currentVersion = $currentPackages[$package]
    $majorVersionLock = $majorVersionLocks.Keys | Where-Object { $package -match $_ }
    if ($majorVersionLock) {
        $newVersion = Get-NugetLatestPackage $package -MajorVersion $majorVersionLocks[$majorVersionLock]
    }
    else {
        $newVersion = Get-NugetLatestPackage $package
    }
    if (-not $newVersion) {
        Write-Error "Unable to find latest version for $package"
    }
    if ($currentVersion.Version -ne $newVersion.Version) {
        Write-Host "Upgrading $package from $($currentVersion.Version) to $($newVersion.Version)"
    }
    $newPackages[$package] = $newVersion
}

$files | ForEach-Object {
    $content = (Get-Content -Raw $_.FullName)
    $newContent = $content
    $newPackages.Keys | ForEach-Object {
        $newVersion = $newPackages[$_]
        $match = "<PackageReference Include=`"$_`" Version=`"(.+?)`""
        if ($newContent -match $match -and $Matches[1] -ne $newVersion.Version) {
            Write-Debug "Upgrading $_ $($Matches[1]) -> $($newVersion.Version)"
            $newContent = $newContent -replace $match, "<PackageReference Include=`"$_`" Version=`"$($newVersion.Version)`""
        }
    }
    if ($content -ne $newContent) {
        Set-Content $_.FullName $newContent
        Write-Host "Modified $($_.FullName)"
    }
}
Prinsn commented 3 years ago

This is nightmarish...

I hope that I can leverage your experience in this issue to at least validate the effort you went through.

@MattJeanes One last thing, however. Is this strictly for external assemblies?

We're currently referencing our internal security resource as a nuget, which is required to be 5.0 which requires EFCore at 5.0, which are executed on by the functions.

I can come up with ways to work around this in the most janktastic fashions, but I'm not sure where the line is drawn between assemblies if this wont fundamentally break everything if I can't completely decouple the functions from solution internal assemblies that reference 5.0.

Kind of fishing for how much Excedrin I should take before trying to fix this.

MattJeanes commented 3 years ago

@Prinsn if your internal assemblies are referencing Microsoft packages it could still be an issue.

We tried to upgrade EF Core to 5.0 as well and because EF Core 5.0 transiently references a bunch of Microsoft.Extensions packages @ 5.0 (you can see that here under dependencies: https://www.nuget.org/packages/Microsoft.EntityFrameworkCore) it caused the Azure Functions to completely break, so we have had to remain on EF Core 3.0 across our solution for now due to this. I also tried trying to manually downgrade the packages it references to 3.0 like Microsoft.Extensions.Logging but that just caused even more problems.

Prinsn commented 3 years ago

This is a literal dumpster fire, I appreciate your sanity checks in this.

MattJeanes commented 3 years ago

Yes it's exactly why dotnet-isolated Azure Functions are a great idea as it avoids all these issues, but it's simply not ready for prime-time when it's missing critical features like the IMessageReceiver with no alternative. Hopefully .NET 6 will fix all of our woes πŸ˜…

Prinsn commented 3 years ago

Unfortunately, I cannot wait 5 months, and downgrading to 3.x is not an option, so we are currently looking into solutions to hack around this.

My cohort is currently trying to figure out if he can create an API service to get an authenticated token to allow us to renew locks via a REST API call, rather than the message receiver.

I'm currently now considering how possible it is to completely remove the functions from our solution and create something of an SDK for decoupling everything to remove any potential referencing issues.

Prinsn commented 3 years ago

My cohort managed to get something promising together by using calls to the azure REST API directly that are promising at least with respect to the lockToken and refreshing locks.

We're going to be testing how it works with deadletter queues tomorrow (since that's like a two step), I hope to have a strategy soon for how to work around the issue.

I don't know if the REST API is robust enough to handle most common cases, but those were the only two things we used the MessageReceiver for.

On Wed, May 19, 2021 at 11:58 AM Matt Jeanes @.***> wrote:

Yes it's exactly why dotnet-isolated Azure Functions are a great idea as it avoids all these issues, but it's simply not ready for prime-time when it's missing critical features like the IMessageReceiver with no alternative. Hopefully .NET 6 will fix all of our woes πŸ˜…

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Azure/azure-functions-dotnet-worker/issues/293#issuecomment-844241563, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAYR7G5KTZUI6VE5PFFO5S3TOPN3JANCNFSM4ZCNDPEA .

-- Jeff Miller

Prinsn commented 3 years ago

Okay, so this isn't gonna be the cleanest. I asked the engineer that worked this through for something of an MVP, and what he did was take what he did, gut all our proprietary stuff, and then put in untested things that approximate something similar.

//TL;DR: We used the REST API for the ServiceBus (e.g.: https://docs.microsoft.com/en-us/rest/api/servicebus/renew-lock-for-a-message)

Support for this is spotty since apparently it hasn't been updated since 2015, so pretty coooooool. You can't obviously interact with the deadletter queue (it says you can only do it based on the parent entity, and the API doesn't support it, so we didn't try), so it might require some work arounds to generate the same basic behavior for any manual deadlettering (i.e.: If you don't need it into DLQ proper and just need to log, you can basically just instantiate a new DLQ and pass in the stuff it would do and have it basically just operate as normal, as much as possible, so that it logs and excepts, then complete the message as normal so it is no longer on the queue) //

His immediate comments were:

I hard coded the resourceUri, topic, and subscription strings. really, they should be pulled from your connection string (assuming it's of the Sas token variety) basic MVP is, if you know the resourceUri of your bus, the topic, and subscription for your message, and the messge ID and lock, you can use those with a POST to renew the token indefinitely all but the last two you should know by virtue of your function running, and the last two you can get from the message itself the topic, and subscription and message id variables in that snippet are not defined for the purposes of demonstration, they can be constants I suppose. the topic and subscription are strings that are needed in the input binding, so you have to have those. the resource Uri, key name, and key, should be in the connection string. the message id and lock are in the message

public class ServiceBusTopicTriggerCSharp1
{
    private readonly Timer lockRenewalTimer;

    public ServiceBusTopicTriggerCSharp1()
    {
        lockRenewalTimer = new Timer(1000 * 60); // renew lock every 60 seconds
    }
    [Function("ServiceBusTopicTriggerCSharp1")]
    public static async Task Run([ServiceBusTrigger("myTopicName", "mySubscription", Connection = "myConnectionString")] string mySbMsg, FunctionContext context, string messageId, string sequenceNumber, string lockToken)
    {
        var logger = context.GetLogger("ServiceBusTopicTriggerCSharp1");
        SetupAutoLockRenewal(messageId, lockToken);
        //function code here
    }
    protected void SetupAutoLockRenewal(string messageId, string lockToken)
    {
        if (lockRenewalTimer.Enabled)
        {
            return;
        }
        lockRenewalTimer.Elapsed += new ElapsedEventHandler(async (object sender, ElapsedEventArgs e) =>
        {
            using var client = new HttpClient();  //TODO: should be dependency injected as a singleton service to avoid port exhaustion issues

            var connectionString = "myConnectionString";  //or pull from settings/keyvault/environment/etc.  must be of the form  Endpoint=sb://{serviceBusResourceUri};SharedAccessKeyName={myKeyName};SharedAccessKey={myKey};TransportType=AmqpWebSockets;
            var resourceUri = connectionString.GetStringBetween("Endpoint=sb://", ";", StringComparison.InvariantCultureIgnoreCase);  //or retrieve/set manually
            var keyName = connectionString.GetStringBetween("SharedAccessKeyName=", ";", StringComparison.InvariantCultureIgnoreCase);  //or retrieve/set manually
            var key = connectionString.GetStringBetween("SharedAccessKey=", ";", StringComparison.InvariantCultureIgnoreCase);  //or retrieve/set manually
            var token = CreateToken(resourceUri, keyName, key);
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", token);

            var url = $"https://{resourceUri}/{topic}/subscriptions/{subscription}/messages/{messageId}/{lockToken}";
            var response = await client.PostAsync(url, null).ConfigureAwait(false);
            if (response.IsSuccessStatusCode)
            {
                //"Message lock successfully renewed"
            }
            else
            {
                //"Message lock renewal failed"
            }
        });
        lockRenewalTimer.Start();
    }
    private static string CreateToken(string resourceUri, string keyName, string key)
    {
        TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
        var week = 60 * 60 * 24 * 7;
        var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + week);
        string stringToSign = HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
        var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
        var sasToken = string.Format(CultureInfo.InvariantCulture, "sr={0}&sig={1}&se={2}&skn={3}", HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry, keyName);
        return sasToken;
    }
    private static string GetStringBetween(this string str, string startAfter, string endAtNext, StringComparison comparisonType)
    {
        int from = str.IndexOf(startAfter, comparisonType) + startAfter.Length;
        int to = str.IndexOf(endAtNext, from, comparisonType);

        return str[from..to];
    }
}

Our only two use cases for the MessageReceiver were to renew the lock and call DeadLetterQueueAsync, so hopefully it at least demonstrates how to interact with the REST API instead of via the Message Receiver for the cases that are supported by the REST API.

Also, it'd be rel kul if the REST API could be updated for the first time in 6 years to cover the gap.

Prinsn commented 3 years ago

Currently seems that this only works when developing locally, the REST API indicates correct lock renewal but doesn't appear to correctly hold the message, and the whole thing is moot as we're seeing database performance degradations almost to an order of magnitude.

It works in some functions but not others

Prinsn commented 3 years ago

@fabiocav Could you explain how this was triaged as an enhancement?

If all developers blocking on this cannot be instructed how to work around this issue to not block adoption of .NET 5.0, this would seem more than an enhancement.

fabiocav commented 3 years ago

@Prinsn this is flagged as an enhancement as this functionality was, by design, not within the scope of the out-of-proc/worker model. This is the same limitation the model currently has for the other languages supported by functions.

Addressing this limitation is an enhancement to what the host supports and to the workers to leverage the new capabilities.

Hope this helps, but can expand if needed.

TheDziekan commented 3 years ago

I was trying to do something like this to go around the issue:

[Function("Function1")]
[ServiceBusOutput("myqueue/$deadletterqueue", Connection = "ServiceBusConnectionString")]
public static string Run([ServiceBusTrigger("myqueue", Connection = "ServiceBusConnectionString")] string myQueueItem, FunctionContext context)
{
    var logger = context.GetLogger("Function1");
    logger.LogInformation($"C# ServiceBus queue trigger function processed message: {myQueueItem}");

    return myQueueItem;
}

But it did not work. Is this problem with ServiceBusOutput not being able to write to $deadletterqueue? Or is it Service Bus (and Azure) designed so that $deadletterqueue can only be used to read?

Prinsn commented 3 years ago

We had to drop our DLQ handling if I recall, as part of our workaround.

On Mon, Jul 5, 2021, 7:46 AM Piotr Dziekanowski @.***> wrote:

I was trying to do something like this to go around the issue:

[Function("Function1")] [ServiceBusOutput("myqueue/$deadletterqueue", Connection = "ServiceBusConnectionString")]public static string Run([ServiceBusTrigger("myqueue", Connection = "ServiceBusConnectionString")] string myQueueItem, FunctionContext context) { var logger = context.GetLogger("Function1"); logger.LogInformation($"C# ServiceBus queue trigger function processed message: {myQueueItem}");

return myQueueItem;

}

But it did not work. Is this problem with ServiceBusOutput not being able to write to $deadletterqueue? Or is it Service Bus (and Azure) designed so that $deadletterqueue can only be used to read?

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Azure/azure-functions-dotnet-worker/issues/293#issuecomment-874049270, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAYR7G4JMMOAXH7YOKZ5NV3TWGLRXANCNFSM4ZCNDPEA .

dvdgutzmann commented 3 years ago

@fabiocav Given .Net 6 being only a couple of weeks away - when can we expect to be able to abandon or complete messages in an out-of-process function worker utilizing the ServiceBusTrigger input binding?

I would have liked to see a clear note in the documentation that standard scenarios are not supported at this time. So, we found out the hard way that we are blocked and now have to backtrack several steps.

dr-consit commented 2 years ago

And now .net 6 is here, and still no support, and nothing in the documentation telling you that this is not achievable?

MattJeanes commented 2 years ago

With .NET 6 you don't need to use the out of process worker for Azure Functions you can just use in-process as you did on .NET Core 3.1. I believe .NET 7 is when the out of process worker is expected to reach feature parity and in fact I believe will be the only way to use Azure Functions on .NET going forward, according to the Azure Functions roadmap: https://techcommunity.microsoft.com/t5/apps-on-azure/net-on-azure-functions-roadmap/ba-p/2197916

dr-consit commented 2 years ago

Which would have been fine, if I hadn't put in the work to migrate to out of process... But I guess it's my fault for jumping the gun...

timgabrhel commented 2 years ago

Which would have been fine, if I hadn't put in the work to migrate to out of process... But I guess it's my fault for jumping the gun...

Same. It's been so long so forgive me if I misremember, but I recall my reason for jumping the gun on the migration was the in-process runtime had a very long lag in supporting the new .NET version releases in production.

That said, does in-process support .NET 6 today, in production?

MattJeanes commented 2 years ago

Azure Functions supports .NET 6 day one in production, so they've at least learnt that lesson from the fun times of .NET 5's launch πŸ˜†

https://azure.microsoft.com/en-gb/updates/generally-available-azure-functions-runtime-40/

Prinsn commented 2 years ago

This graph suggests they work in 5, when 5 broke everything, so I'm confused.

We literally had to rollback from 5 because of this

On Tue, Nov 9, 2021, 10:10 AM Matt Jeanes @.***> wrote:

With .NET 6 you don't need to use the out of process worker for Azure Functions you can just use in-process as you did on .NET Core 3.1. I believe .NET 7 is when the out of process worker is expected to reach feature parity and in fact I believe will be the only way to use Azure Functions on .NET going forward, according to the Azure Functions roadmap: https://techcommunity.microsoft.com/t5/apps-on-azure/net-on-azure-functions-roadmap/ba-p/2197916

https://camo.githubusercontent.com/2da0beb70f59c48f3bfaf88064cf8d591c36a7fdf32f1c8a1b5e82ee1c891d23/68747470733a2f2f6d61726b68656174682e6e65742f706f7374732f323032312f646f746e65742d66756e6374696f6e732d726f61646d61702e706e67

β€” You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/Azure/azure-functions-dotnet-worker/issues/293#issuecomment-964241329, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAYR7G3LGAF3CM3ZGYOJK33ULE2VLANCNFSM4ZCNDPEA . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

MattJeanes commented 2 years ago

I think all we can be sure of is that come .NET 7, everything will be ready to go as that will be the only supported method of running Azure Functions then. Out of process still seems very much an afterthought even in .NET 6. Would love to hear some more details of what's new with OOP Azure Functions on .NET 6 and see if any of these concerns have been resolved.

digitalkirkley commented 2 years ago

Have any of the concerns with OOP Azure Functions in .NET 5 been resolved in .NET 6? E.g. MessageReceiver, ServiceBusReceivedMessage etc

SeanFeldman commented 2 years ago

@digitalkirkley no. Perhaps some discussions or work are taking place, but it's not reflected here.

I've run into this pain point just today with messages that need to be dead-lettered (#381) once the message fails to process for the first time, and it's not possible, which forces the unnecessary re-processing based on the number of retries defined, adding unneeded AppInsights logs and increasing the cost of the system.

@fabiocav, is there a discussion anywhere about the plans for .NET 7 when the two SDKs will converge?

aliqaryan commented 2 years ago

I really need IMessageSession messageSessionReceiver as an input parameter in Azure Function ServiceBusTrigger, It was supported before but not anymore, and now I have to refactor all of my microservices because of that!!!!!! Please help me out here.

SeanFeldman commented 2 years ago

@aliqaryan2, this SDK has never supported native SDK types so not sure how could you upgrade all of your ASB-dependent code w/o noticing the obvious. Nonetheless, you can still use the in-process SDK and access MessageReceiver until there's either a better story for the Isolated SDK or a definitive answer about the fate of native SDK types support.

Note that this issue is neither associated with a milestone nor is referenced by any other issue. Might want to pack up some patience until it happens.

cosminstirbu commented 2 years ago

@fabiocav - can we have some sort of confirmation that this limitation will be fixed in .NET 7?

Prinsn commented 2 years ago

I'm still subbed to this thread, but I'm having a hard time following it.

What limitation are you citing?

I'm currently upgrading to 6 and it required very few modifications. 5 and the isolated processes were the breaking change.

Based on my understanding, odd numbers going forward (including 5) are betas for even numbers, so relying on 7 might not be smart

On Fri, Aug 19, 2022, 4:31 AM Cosmin Stirbu @.***> wrote:

@fabiocav https://github.com/fabiocav - can we have some sort of confirmation that this limitation will be fixed in .NET 7?

β€” Reply to this email directly, view it on GitHub https://github.com/Azure/azure-functions-dotnet-worker/issues/293#issuecomment-1220400566, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAYR7G6PZN54WXQYDRYJ2O3VZ5A73ANCNFSM4ZCNDPEA . You are receiving this because you were mentioned.Message ID: @.***>

SeanFeldman commented 2 years ago

What limitation are you citing?

Limitations associated with the lack of

I'm currently upgrading to 6 and it required very few modifications.

Anyone who's not doing anything beyond the standard scenario of receiving and sending is not impacted. You're lucky to be in that groupπŸ™‚

aliqaryan commented 2 years ago

@aliqaryan2, this SDK has never supported native SDK types so not sure how could you upgrade all of your ASB-dependent code w/o noticing the obvious. Nonetheless, you can still use the in-process SDK and access MessageReceiver until there's either a better story for the Isolated SDK or a definitive answer about the fate of native SDK types support.

Note that this issue is neither associated with a milestone nor is referenced by any other issue. Might want to pack up some patience until it happens.

With the old library, I could use my own duplicate msg detection by Peek message sessions to see if we have existing message session in the serviceBus queue (because azure service bus duplicate msg detection is not practical for recurring messages(jobs) ). I implemented the mechanism in az function by adding IMessageSession messageSession as an input param to the ServiceBus Trigger function, then by calling messageSession.PeekAsync() I could detect duplicate sessions in the queue and do a job accordingly. this is not possible with the new library anymore so I have to change the whole logic :( that need so much effort.

Prinsn commented 2 years ago

I mean, I literally wasted like 2 weeks trying to upgrade because of isolated process workers in 5

On Fri, Aug 19, 2022, 12:17 PM Sean Feldman @.***> wrote:

What limitation are you citing?

Limitations associated with the lack of

  • Support for native types or ASB message metadata
  • Inability to control message dispositioning
  • Transactional messaging support
  • Ability to create senders w/o instantiating a new physical connection

I'm currently upgrading to 6 and it required very few modifications.

Anyone who's not doing anything beyond the standard scenario of receiving and sending is not impacted. You're lucky to be in that groupπŸ™‚

β€” Reply to this email directly, view it on GitHub https://github.com/Azure/azure-functions-dotnet-worker/issues/293#issuecomment-1220858278, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAYR7G3X34DBQURX5CWAV23VZ6XSNANCNFSM4ZCNDPEA . You are receiving this because you were mentioned.Message ID: @.***>

Archomeda commented 2 years ago

Indeed, ServiceBus triggers in isolated processes do not have feature parity with their in-process counterpart yet. Which is also why we have reverted to in-process recently. I do not understand how this went GA in its current state.

djfoxer commented 2 years ago

@Prinsn I think that I found how to do that, assume I want to Complete message, so I have to do in this way:


[FunctionName(nameof(MyServiceBusTrigger))]
        public async Task Run(
            [ServiceBusTrigger(MyQueue, Connection = MyQueueConnection, AutoCompleteMessages = false)]
            ServiceBusReceivedMessage message,
            ServiceBusMessageActions messageActions
            )
        {
            await messageActions.CompleteMessageAsync(message);
        }
SeanFeldman commented 2 years ago

@djfoxer, what you're referring to is the InProcess SDK. This issue/repo is about Isolated Worker SDK. The earlier has always worked with native ASB SDK types, and message dispositioning was possible.

djfoxer commented 2 years ago

@SeanFeldman Maybe I misunderstood something but my working code is from Worker in .NET 5 where Program.cs (console app) has HostBuilder (DI and so on). Why it's not isolated process?

SeanFeldman commented 2 years ago

In-Process SDK runs on .NET 5/6 as well. It's not about .NET but instead the packages your Functions App is pulling in. Looks at your CSPROJ and see what you're using.