Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proto.Promises.PromiseYieldExtensions exception on Android? #512

Closed
drew-512 opened this issue Dec 31, 2024 · 7 comments · Fixed by #513 or #514
Closed

Proto.Promises.PromiseYieldExtensions exception on Android? #512

drew-512 opened this issue Dec 31, 2024 · 7 comments · Fixed by #513 or #514
Assignees
Labels
bug Something isn't working
Milestone

Comments

@drew-512
Copy link

I'm turning the corner on this forthcoming release (excited to show ya Tim!) and just recently have an Android build since a long period. I'm getting a 100% reproducible exception within PP:

Exception: Exception: Failed to read 'jar:file:///data/app/~~fvfP2-kEXeOGS9xNYiyy2w==/com.soundpspectrum.tunr--JUWMsMNG0Kq_1ryachGLQ==/base.apk!/assets/Bundled/app.vars.factory.json'
System.NullReferenceException: Object reference not set to an instance of an object.
  at Proto.Promises.PromiseYieldExtensions+AwaitInstructionAwaiter`1[TAwaitInstruction].OnCompleted (System.Action continuation) [0x00000] in <00000000000000000000000000000000>:0 
  at Proto.Promises.Internal+CriticalAwaitOverrider`1[TAwaiter].DefaultAwaitOnCompleted (TAwaiter& awaiter, Proto.Promises.Internal+PromiseRefBase asyncPromiseRef, System.Action continuation) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.ReadAllBytes (System.String absPath, System.Boolean suppressFNF) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.AssetService.loadTextureUsingWebRequest (Amp.AssetRequest assetReq) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.ReadAllBytes (System.String absPath, System.Boolean suppressFNF) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.ReadAllText (System.String absPath, System.Boolean suppressFNF) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.AssetService.loadTextureUsingWebRequest (Amp.AssetRequest assetReq) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.ReadAllText (System.String absPath, System.Boolean suppressFNF) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.readAppVars () [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.AssetService.loadTextureUsingWebRequest (Amp.AssetRequest assetReq) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.readAppVars () [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.OnApplicationStart () [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.AssetService.loadTextureUsingWebRequest (Amp.AssetRequest assetReq) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.OnApplicationStart () [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.Awake () [0x00000] in <00000000000000000000000000000000>:0 
  at System.Runtime.CompilerServices.AsyncVoidMethodBuilder.Start[TStateMachine] (TStateMachine& stateMachine) [0x00000] in <00000000000000000000000000000000>:0 
  at Amp.App.Awake () [0x00000] in <00000000000000000000000000000000>:0 

Offending function:

        public static async Promise<byte[]>     ReadAllBytes(string absPath, bool suppressFNF = false) {
            byte[] buf = null;
            string err = null;
            
            if (absPath.StartsWith("jar:file:")) {
                using (var req = UnityWebRequest.Get(absPath)) {
                    req.downloadHandler = new DownloadHandlerBuffer();
                    await PromiseYielder.WaitForAsyncOperation(req.SendWebRequest());  // << Exception 
                    ...
                }
            } else {
                ...
            }
            ...
            return buf;
        }

Before the switch to PP, the above function look like and was in production:

        public static async Task<byte[]>        ReadAllBytes(string absPath, bool suppressFNF = false) {
            byte[] buf = null;
            string err = null;
            
            if (absPath.StartsWith("jar:file:")) {
                using (var req = UnityWebRequest.Get(absPath)) {
                    req.downloadHandler = new DownloadHandlerBuffer();
                    await req.SendWebRequest();
                    ...
                }
            } else {
                ...
            }
            ...
            return buf;
        }

Looking at the stack trace, it's suspicious to see another async call loadTextureUsingWebRequest is unexpectedly interleaved and thinking I'm doing something fooling elsewhere? For reference, here's those other (unrelated but possibly concurrent) functions:

        AsyncSemaphore _maxFileLoads = new AsyncSemaphore(4);
        AsyncSemaphore _maxWebReqs   = new AsyncSemaphore(2);

        async void loadTextureFromFile(AssetRequest texReq, string pathname) {
            await Promise.SwitchToBackgroundAwait(true);
            
            byte[] texData = null;
            using (await _maxFileLoads.EnterScopeAsync()) {
                ...
                try {
                    texData = File.ReadAllBytes(pathname);
                } catch (Exception e) {
                    texReq.Fail($"failed to read file '{pathname}': {e.Message}");
                    return;
                }
            }
            
            await Promise.SwitchToForegroundAwait();

            Texture2D tex2D = new Texture2D(1,1);
            if (tex2D.LoadImage(texData)) {
                texReq.Resolve(tex2D);
            } else {
                Destroy(tex2D);
                texReq.Fail($"failed to load texture from file '{pathname}'");
            }
        }
        
        async void loadTextureUsingWebRequest(AssetRequest assetReq) {
            using (await _maxWebReqs.EnterScopeAsync()) {
                string url = assetReq.Get.URL;
                using (var req = UnityWebRequestTexture.GetTexture(url, false)) {
                    await PromiseYielder.WaitForAsyncOperation(req.SendWebRequest());
                    
                    if (req.result == UnityWebRequest.Result.Success) {
                        assetReq.Resolve(((DownloadHandlerTexture) req.downloadHandler).texture);
                    } else {
                        assetReq.Fail(req.error);
                    }
                }
            }
        }
@timcassell
Copy link
Owner

timcassell commented Dec 31, 2024

Are you able to reproduce in editor by chance? Or is it only happening on device?

@timcassell
Copy link
Owner

Also, at what point in the program is that running? Before Awake? Looking at the code, the only thing that could be null there is InternalHelper.PromiseBehaviour.Instance which gets initialized in [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)], so if it's running before that it could break.

@timcassell timcassell self-assigned this Dec 31, 2024
@timcassell timcassell added the bug Something isn't working label Dec 31, 2024
@drew-512
Copy link
Author

I don't have the setup for a simulator and this is on my vanilla testing Android I'm using.

The call happens from Awake() on my root "App" MonoBehavior:

        
        async void                          Awake() {
            ...
            DontDestroyOnLoad(this.gameObject);
            await OnApplicationStart();
            ...
        }
        
    
        public async Promise                OnApplicationStart() {
            ...
            await readAppVars();
            ...
        }
        

@timcassell
Copy link
Owner

Does #513 fix it?

@drew-512
Copy link
Author

drew-512 commented Jan 1, 2025

Fix confirmed good to go, appreciate your diligence!

@timcassell timcassell added this to the v3.3.0 milestone Jan 2, 2025
@timcassell
Copy link
Owner

timcassell commented Jan 4, 2025

Well, I found the actual cause. I create the PromiseBehaviour instance in BeforeSceneLoad, but I don't actually assign the s_instance field until Start. So your Awake is being ran before it. I'm surprised you didn't observe the same bug in editor. I don't remember the exact reason why I did that, probably some weird Unity quirk I was trying to work around.

So #513 was only a half-fix. I'm working on a comprehensive fix now.

@timcassell timcassell reopened this Jan 4, 2025
@timcassell timcassell linked a pull request Jan 4, 2025 that will close this issue
@drew-512
Copy link
Author

drew-512 commented Jan 5, 2025

ah, ok great -- appreciate your efforts!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
2 participants