Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

[BUG] CacheInitialization deadlocks and prevents Addressables initialization

Discussion in 'Addressables' started by phobos2077, Apr 15, 2020.

  1. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    If CacheInitialization is used and Addressable initialization process is started too soon (before Caching.ready == true) the CacheInitOp.Update is called only once (from Execute) and the operation will never complete. Addressables won't initialize because it depends on this operation and Update will never be called again to complete CacheInitOp and un-block initialization.

    Unity: 2019.3.9f1
    Addressables: 1.7.5
    Platform: Android il2cpp

    Steps to reproduce:
    • Create CacheInitializationSettings scriptable object.
    • In AddressablesAssetSettings add this object in Initialization Objects list.
    • Create a MonoBehavior in the first scene that calls Addressables.LoadAssetAsync() from Awake() and prints something after the returned AsyncOperationHandle is completed.
    • Switch to Android platform and enable IL2CPP.
    • Build addressables content.
    • Build Android app.
    • Deploy and run the app on device.
    You might need to try that several times to reproduce the issue because there is a race condition between Caching.ready being set to true and CacheInitialization.CacheInitOp.Execute() being called. If you can't reproduce the issue, just look at the code closely, the issue should be very clear.
     
  2. TreyK-47

    TreyK-47

    Unity Technologies

    Joined:
    Oct 22, 2019
    Posts:
    1,795
  3. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    Sent, Case 1238594.

    Because I couldn't wait and tolerate the bug, here's how I've worked around it in my case:

    - Created a copy of CacheInitialization class (w/ subclass)
    - Removed logic from Update method and instead written an async void method that is called from Execute and awaits new WaitForEndOfFrame() in a loop until Caching.ready is true and then calls Complete (so doesn't rely on Update at all)
    - Created a copy of CacheInitializationSettings class that points to my custom CacheInitalization.
    - Created an asset of my CustomCacheInitializationSettings
    - Replaced the CacheInitializationSettings with my CustomCacheInitializationSettings in the Initialization Objects list
     
    TreyK-47 likes this.
  4. NamelessPerson

    NamelessPerson

    Joined:
    Apr 17, 2017
    Posts:
    26
    @phobos2077 I believe I am having this same issue in a windows build. Would you be able to post your modified code that you got working?
     
  5. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
    Code (CSharp):
    1. [Serializable]
    2.     public class CustomCacheInitialization : IInitializableObject
    3.     {
    4.         /// <summary>
    5.         /// Sets properties of the Caching system.
    6.         /// </summary>
    7.         /// <param name="id">The id of the object.</param>
    8.         /// <param name="dataStr">The JSON serialized CacheInitializationData object.</param>
    9.         /// <returns>True if the initialization succeeded.</returns>
    10.         public bool Initialize(string id, string dataStr)
    11.         {
    12. #if ENABLE_CACHING
    13.             var data = JsonUtility.FromJson<CacheInitializationData>(dataStr);
    14.             if (data != null)
    15.             {
    16.                 Caching.compressionEnabled = data.CompressionEnabled;
    17.                 var activeCache = Caching.currentCacheForWriting;
    18.                 if (!string.IsNullOrEmpty(data.CacheDirectoryOverride))
    19.                 {
    20.                     var dir = Addressables.ResolveInternalId(data.CacheDirectoryOverride);
    21.                     if (!Directory.Exists(dir))
    22.                     {
    23.                         Directory.CreateDirectory(dir);
    24.                     }
    25.  
    26.                     activeCache = Caching.GetCacheByPath(dir);
    27.                     if (!activeCache.valid)
    28.                     {
    29.                         activeCache = Caching.AddCache(dir);
    30.                     }
    31.  
    32.                     Caching.currentCacheForWriting = activeCache;
    33.                 }
    34.  
    35.                 activeCache.maximumAvailableStorageSpace = data.LimitCacheSize ? data.MaximumCacheSize : long.MaxValue;
    36.  
    37.                 activeCache.expirationDelay = data.ExpirationDelay;
    38.             }
    39. #endif //ENABLE_CACHING
    40.             return true;
    41.         }
    42.  
    43.         /// <inheritdoc/>
    44.         public AsyncOperationHandle<bool> InitializeAsync(ResourceManager rm, string id, string data)
    45.         {
    46.             var op = new CacheInitOp();
    47.             op.Init(() => Initialize(id, data));
    48.             return rm.StartOperation(op, default);
    49.         }
    50.  
    51.         private class CacheInitOp : AsyncOperationBase<bool>, IUpdateReceiver
    52.         {
    53.             private Func<bool> _callback;
    54.  
    55.             public void Init(Func<bool> callback)
    56.             {
    57.                 _callback = callback;
    58.             }
    59.  
    60.             public void Update(float unscaledDeltaTime)
    61.             {
    62.  
    63.             }
    64.  
    65.             private async void TryInit()
    66.             {
    67. #if ENABLE_CACHING
    68.                 Addressables.Log($"CacheInit.TryInit()");
    69.                 while (!Caching.ready)
    70.                 {
    71.                     await new WaitForEndOfFrame();
    72.                 }
    73.  
    74.                 Addressables.Log($"CacheInit.TryInit(): caching is ready!");
    75.  
    76.                 Complete(_callback == null || _callback(), true, "");
    77.  
    78.                 Addressables.Log("CacheInitialization Complete");
    79. #else
    80.                 Complete(true, true, "");
    81.                 Addressables.Log("UnityEngine.Caching not supported on this platform but a CacheInitialization object has been found in AddressableAssetSettings. No action has been taken.");
    82. #endif
    83.             }
    84.  
    85.             protected override void Execute()
    86.             {
    87.                 Addressables.Log($"CacheInit.Execute()");
    88.                 TryInit();
    89.             }
    90.         }
    91.     }

    Code (CSharp):
    1.  
    2. [CreateAssetMenu(fileName = "CacheInitializationSettings.asset", menuName = "Addressables/Initialization/Cache Initialization Settings (fixed)")]
    3.     public class CustomCacheInitializationSettings : ScriptableObject, IObjectInitializationDataProvider
    4.     {
    5.         [SerializeField]
    6.         private CacheInitializationData _data = new CacheInitializationData();
    7.  
    8.         /// <summary>
    9.         /// Display name used in GUI for this object.
    10.         /// </summary>
    11.         public string Name => "Asset Bundle Cache Settings (fixed)";
    12.  
    13.         /// <summary>
    14.         /// Create initialization data to be serialized into the Addressables runtime data.
    15.         /// </summary>
    16.         /// <returns>The serialized data for the initialization class and the data.</returns>
    17.         public ObjectInitializationData CreateObjectInitializationData()
    18.         {
    19.             return ObjectInitializationData.CreateSerializedInitializationData<CustomCacheInitialization>(typeof(CustomCacheInitialization).Name, _data);
    20.         }
    21.     }
     
  6. NamelessPerson

    NamelessPerson

    Joined:
    Apr 17, 2017
    Posts:
    26
    @phobos2077 Do you have a custom defined WaitForEndOfFrame? I am getting an error that no awaiter is defined.
     
  7. phobos2077

    phobos2077

    Joined:
    Feb 10, 2018
    Posts:
    350
  8. NamelessPerson

    NamelessPerson

    Joined:
    Apr 17, 2017
    Posts:
    26
    Gotcha. I ended up using this similar package: https://github.com/zsaladin/Asyncoroutine

    Thanks for the code it worked perfectly for me! Just as a note for anyone else who needs this, it did require me to update the previous content build for the new initialization settings to take effect.
     
    phobos2077 likes this.