CesiumGS / cesium-unity

Bringing the 3D geospatial ecosystem to Unity
https://cesium.com/platform/cesium-for-unity/
Apache License 2.0
358 stars 83 forks source link

Crash in `CesiumIonSessionImpl` during Unity code reloading #432

Closed azrogers closed 3 months ago

azrogers commented 8 months ago

Same issue I wrote about on the forums: https://community.cesium.com/t/crash-in-cesium-for-unity-during-code-reloading/29544

Occasionally during code reloading, Cesium for Unity will crash due to a read access violation coming from one of two lines, both in CesiumIonSessionImpl.cpp. First, on line 266:

// Verify that the connection actually works.
pConnection->me()
    .thenInMainThread(
        [this, pConnection](
            CesiumIonClient::Response<CesiumIonClient::Profile>&& response) {
        logResponseErrors(response);
        if (response.value.has_value()) {
            this->_connection = std::move(*pConnection); // CRASH HERE
        }
        this->_isResuming = false;
        this->_quickAddItems = nullptr;
        this->broadcastConnectionUpdate();

        this->startQueuedLoads();
    })
    .catchInMainThread([this](std::exception&& e) {
        logResponseErrors(e);
        this->_isResuming = false;
    });

And second on line 383:

this->_connection->me()
    .thenInMainThread(
        [this](
            CesiumIonClient::Response<CesiumIonClient::Profile>&& profile) {
        this->_isLoadingProfile = false;
        this->_profile = std::move(profile.value);
        this->broadcastProfileUpdate();
        if (this->_loadProfileQueued)
            this->refreshProfile();
    })
    .catchInMainThread([this](std::exception&& e) {
        this->_isLoadingProfile = false;
        this->_profile = std::nullopt; // CRASH HERE
        this->broadcastProfileUpdate();
        if (this->_loadProfileQueued)
            this->refreshProfile();
    });

These crashes seem to happen when one of the above lines runs after the CesiumIonSessionImpl object has been destroyed. I believe this happens when two code reloads happen in quick succession after one another - a new session is created after one code reload only to be destroyed by the start of the second, before the callback has finished. Unfortunately, putting session into the captures of the two lambdas above doesn't seem to fix the issue.

I've been able to get this crash to happen using this edit-mode test:

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
using UnityEngine.TestTools;

public class TestCesiumIonSession
{
    [UnityTest]
    public IEnumerator CrashWhenResumingSession()
    {
        EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows);

        yield return new EnterPlayMode(true);
        yield return new ExitPlayMode();

        CompilationPipeline.RequestScriptCompilation();
        EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
        yield return null;

        yield return new EnterPlayMode(true);
        yield return new ExitPlayMode();

        EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows);
    }
}

I'm not sure how many of these lines are needed, but this seems to make the crash happen about 2/3rds of the time when run. Sometimes it takes a few repeated runs to happen. It seems to happen more often when Unity is compiling in the background than when focused in the foreground, but I might just be imagining this.