Essentials: Location request failing to return or throw after permissions request when called from F# code.

Created on 13 Jun 2018  路  38Comments  路  Source: xamarin/Essentials

EDIT : Example a couple of posts down

Description

When I try to get location on iOS from my F# core library, I find that exceptions, such as caused by refusing permission, fail to throw.

If I call the same code from C#, it behaves as expected. I even tried making the call to the plugin in a C# method, and calling _that_ method from F#, but I found the same result.

I also find that F# does not continue the location request after granting permission, wheras C# does. In both cases, exceptions and permissions, the async location request never returns.

Steps to Reproduce

EDIT : Use the sample posted below

Expected Behavior

A PermissionException should be thrown

Actual Behavior

Nothing happens, the task never returns

Basic Information

  • Version with issue: 0.6.0
  • Last known good version: None
  • IDE: Visual Studio 2017
  • Platform Target Frameworks: 9.3 and up

    • iOS: 11.4

bug need-more-information

Most helpful comment

Oddly enough the F# VSTS issue seems to be back, except now it's forcing v4.5.2 (we are using anonymous records so we need at least 4.6). I've raised two separate issues with Microsoft over this, they keep closing it saying it is fixed but it's not. Here's the link to one of them: https://developercommunity.visualstudio.com/content/problem/712960/f-46-language-features-dont-work-on-vs-for-mac-sta.html

Can I get some clarification on the local agent workaround that's mentioned here? This has stalled us for over a month so far.

All 38 comments

Created a standalone issue

Ok I have managed to reproduce it. Just deny permissions when asked, then alternate between calling the location service from C# and F# by commenting / uncommenting the appropriate line in the view controller. C# will throw a permission exception in the Location service and print it to the console. F# will never throw or return from the Task.
Example.zip

I realise this might be an issue with the way F# async and C# Tasks interact, but it seems like it should work to the layman at least. If I make a simple task which just throws an exception, then it will throw when I run it from either language.


So, actually this is working fine for me with your sample. You need to set your iOS simulator to have a location. Else it doesn't have anything and it gets locked waiting for you to set it. We will add this to documentation.

Hi James,
Thanks for looking at this for me. I'm not totally sure we are on the same page here, as you initially said couldn't "reproduce this in C#", but the problem is specifically with F#. Also the problem isn't getting location to work, it's catching exceptions when it doesn't.

It also can't be a simulator problem as I observe different behaviour between the two languages when running on the same simulator.

Can you tell me - if you run my sample as a fresh installation, with the C# library call from the view controller active, and the F# lib call commented out, then deny location permission, do you see a location exception raised in the location service? I have caught it and printed to the console fyi, so it won't crash.

Then, comment out the c# call and uncomment the call to the F# lib, then again run a fresh installation and deny permission. This time the exception is not raised.

Do you see anything different? Can you get any exception to propagate on F#?

So, i am able to reproduce this issue in C# or F#. It is when the location is not set on the simulator. It is an issue with the iOS simulator. Basically we make a call to the location manager and it never returns and no exception is caught at all. When you set the location it returns when you make the call.

However reading further perhaps that is a different issue from what you are seeing, so let me try to start over at the original one. Thanks for following up on it.

So, yes I do see this issue in your sample application that the exception does not get thrown for you some reason in that C# code...

However in our sample application and test app with just C# it works just fine...

I'm not really an F# person, but I have to imagine it is not with our library at all. If it works from C# then should from F#... I will inquire more though.

Hi James,

I assume you meant "the exception does not get thrown for you some reason in that F# code...".

Again, thanks for digging at it for me. In my production code I wasn't even calling your plugin via c#, I was calling it directly from F#, so even simpler than the example. Basically this means that your plugin can't be used from F# which is a shame, and I guess this might affect others too perhaps?

For example, just start a plain F# project, no C# code at all, and call your plugin and deny permission, you will find the same.

I'm just starting to do my core work in F#, with my VMs and Xamarin code in C#, so I am probably in a fairly untested situation here.

I'm going to start work now and listen to Merge Conflict, cheers!

Can we also open this as a bug again, as it wasn't solved? Thanks.

It is open :)

I will say that I was able to get results back just fine when the user accepted the permissions here so the library itself would work just fine. My assumption here is that something in the F# call is block or swallowing the exception as the app is still responsive.

My thinking is the F# code is wonky perhaps or something is happening there. I am going to look further here.

Hey James,

Sorry I meant add the bug tag, or at least remove the 'invalid, unable to reproduce' as it does exist and you could reproduce it with my sample, and others may pick it up if they see it - I was just worried it might be ignored otherwise.

I did think it might be the way the task completions are being handled between the C# Task and F# Async systems, it feels like the likely cause. Basic exceptions work fine between the systems, but I think maybe there is something in the location plugin which has exposed a more complex error case.

I would say that being able to catch the location denied permission is a vital part of the plugin, although granted it may not be your error as such.

Also it isn't limited to the exceptions, for example the initial location is never returned if you do accept the permission. You have to stop and start updates again to get it. Again, it's the continuation after the request that is the problem.

It definitely isn't my simulator location, which is set and also location updates work fine after stopping / starting them post-acceptance.

Thanks again for digging,
Ryan

Thanks :) Do you reckon it might be worth me posting this on the F# repo issues, and linking them? Since it may well be that end of things.

I have asked our F# team to take a look here.

Awesome, you're a legend :)

@RyanBuzzInteractive I'm not certain but it is possible you need this or some variation of this: http://www.fssnip.net/7Rc/title/AsyncAwaitTaskCorrect

However I think that's to avoid seeing AggregateException (and see the specific exception instead), so I'm not certain it deals with "Nothing happens, the task never returns"

I'll be able to help with this tomorrow, unfortunately I need to go out now.

Hey @dsyme , thanks for that. I actually found that exact post and tried it but it didn't seem to make a difference.

@RyanBuzzInteractive You might want Async.StartAsTaskImmediate. That starts on the current thread (likely the UI thread) rather than in the thread pool

Thanks, I'll check that out.

I should also reiterate that although the title of this bug relates to exceptions which aren't thrown, as I explained above I also never see the task return with location even if I do grant permission.

If I request a second time (permissions are already granted) then it works no problem, so I feel it is related to the permissions completion (or how I am calling the plugin, or how F# is dealing with it, but related to that bit)

Specifically this bit:

    static Task<PermissionStatus> RequestLocationAsync()
    {
        locationManager = new CLLocationManager();

        var tcs = new TaskCompletionSource<PermissionStatus>(locationManager);

        locationManager.AuthorizationChanged += LocationAuthCallback;
        locationManager.RequestWhenInUseAuthorization();

        return tcs.Task;

        void LocationAuthCallback(object sender, CLAuthorizationChangedEventArgs e)
        {
            if (e.Status == CLAuthorizationStatus.NotDetermined)
                return;

            locationManager.AuthorizationChanged -= LocationAuthCallback;
            tcs.TrySetResult(GetLocationStatus());
            locationManager.Dispose();
            locationManager = null;
        }
    }

Updated the title to try to be more accurate. Feel free to reword it though, still feels clunky...

Maybe local function issues with F#?

That would be so ironic ^_^

It is either that or the garbage collector is picking something up before it can finish....

This may be it perhaps it is the tcs... Although weird that the functionality would be different between C# and F#...

I worked with @praeclarum on this and we got it working just fine with ContinueWith. So this is an F# issue or the code is implemented in a way that it is gobbling up the exception.

Here is the new code:

namespace FSharpLib

module Library =
    open CSharpLib
    open System.Threading.Tasks

    type FSharpClass () =

        member this.TryGetLocation () =
            LocationService.GetLocation().ContinueWith(fun (t:Task<_>) -> printfn "Task %A" t)

Here is the output:

[0:] Location Permissions needed
Thread started: <Thread Pool> #4
Thread started: <Thread Pool> #5
Thread started: <Thread Pool> #6
Thread started: <Thread Pool> #7
2018-06-21 11:24:06.141 Example.iOS[90527:6425030] Task System.Threading.Tasks.Task`1[Xamarin.Essentials.Location]
[0:] Completed async request

Ok, I think I see what you did - you took the f# async out of the equation, you are directly returning the task from your plugin. Of course that would work, as it is literally the same as calling it from C#.

Unfortunately that doesn't help at all, as if you want to use the location in F# you have to await the plugin, which means doing AwaitTask.

Then up the chain when you want to use your async F# routine, that has to be converted to a Task.

This is why I set the example up the way I did.

F#:
async
{
// Get location
// USE location for something, get data of some sort
// return that data
}

F#:
Wrap async function in a Class method which returns a Task for C# consumption

C#:
// Await that F# method to get data

This is the structure of my large enterprise project that I am working on, hundreds of these kinds of things, and this is the only one I have had issues with. I have worked with JSON.Net and SQLite-net and all the other usual suspects and they have behaved totally normally, exceptions, returning data, all of it.

Again, I totally realise this is probably some magic in the async / task conversion but is that because the language is broken or we are calling it incorrectly? I'm not sure, I just want to know how to get around it!

EDIT: SOLVED in referenced issue :) Thanks guys!

A quick note - my build is now failing in VS Mac as that doesn't support StartAsTaskImmediate yet. This isn't a bug with the plugin of course but does mean it can't be used on a Mac with F# until that arrives.

A quick note - my build is now failing in VS Mac as that doesn't support StartAsTaskImmediate yet. This isn't a bug with the plugin of course but does mean it can't be used on a Mac with F# until that arrives.

You can upgrade your project to use FSHarp.Core 4.5.0. There wsa some bug with VS for Mac that was automatically downgrading 4.3.4 but that has been fixed, you may need to manually edit your project file until then

cc @nosami

great, thanks :)

That fixed it on my Mac but it still fails on VSTS, I can see that it is being downgraded to 4.2.3 even though I have explicitly added 4.5.0 from nuget in every project and even tried added a binding redirect. I'm sure it will go away once the VSTS machine is updated.

It is most likely that the VSTS hosted agent isn't updated with the latest build. I would recommend running the agent local :) this is what I do.

Thanks James, I'll look at that if it isn't sorted soon.

Hi again @jamesmontemagno , I just had a thought. I noticed you guys added a MainThreadDispatch plugin to Essentials recently and said that you use it internally already.

Since my problem was that I was unknowingly requesting Location on a background thread, would it be possible for you to force the plugin to run the permissions request on the UI thread and then return the result on the original calling thread? Then fools like me can't make mistakes hehe.

Cheers!

Heya, yeah I have seen it thanks, that's what I was referring to in my previous message. I was just suggesting that the location plugin marshals any calls to it onto the main thread automatically, wherever they come from (or throw an exception like Don suggested, so it is clear what is going on, rather than just go silent). That way people couldn't make the same mistake I did.

Is there anything in the documentation that says that the plugin must be used from the UI thread? I mean I guess it is common sense but could be good to spell it out?

ah, i see what you are saying about the permission prompt as that would come from the UI thread itself.
i mean what we should do is basically detect if we need to prompt for permissions and if you are on a background thread then not allow it. can you file a new issue with more details on the issue you are seeing

sure ok :)

I know this is long closed but just thought I would add that the F# VSTS issue I mentioned has been fixed now :) https://developercommunity.visualstudio.com/content/problem/393932/mac-os-builds-are-broken-when-using-modern-f.html

Oddly enough the F# VSTS issue seems to be back, except now it's forcing v4.5.2 (we are using anonymous records so we need at least 4.6). I've raised two separate issues with Microsoft over this, they keep closing it saying it is fixed but it's not. Here's the link to one of them: https://developercommunity.visualstudio.com/content/problem/712960/f-46-language-features-dont-work-on-vs-for-mac-sta.html

Can I get some clarification on the local agent workaround that's mentioned here? This has stalled us for over a month so far.

Was this page helpful?
0 / 5 - 0 ratings