Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by htudrshtsrhtsthsht · Oct 21, 2014 at 04:33 PM · ioswwwxcodememory-leak

IOS memory leak because of WWW object

Hi there,

I have a nifty memory leak when I run my project on the IOS platform. I have checked it using instruments leak tool and debug using Xcode 6. In some cases it looks like the WWW object is never disposed.


I°) Here is the part of code that handle sending WWW requests:

 private void issueRequestWithoutQueuing(string url, Dictionary<string, string> parameters = null, Callback<ServerData> successCallback = null, Callback<string> apiFailureCallback = null, Callback<string> layerFailureCallback = null, bool showSpinner = false)
 {
     QueuedWWWObject queueWWW = new QueuedWWWObject();
     queueWWW.url = url;
     queueWWW.successCB = successCallback;
     queueWWW.apiFailureCB = apiFailureCallback;
     queueWWW.layerFailureCB = layerFailureCallback;
     if (parameters != null)
     {
         queueWWW.parameters = parameters;
     }
     issueRequest(queueWWW, showSpinner);
 }
 
     private void issueRequest(QueuedWWWObject queueWWW, bool showSpinner = false)
     {
         numberRequestInProgress ++;
 
         WWW www;
         if (queueWWW.parameters != null && queueWWW.parameters.Count != 0)
         {
             WWWForm form = new WWWForm();
             foreach (KeyValuePair<string, string> pair in queueWWW.parameters)
             {
                 form.AddField(pair.Key, pair.Value);
             }
             www = new WWW(queueWWW.url, form);
         }
         else
         {
             www = new WWW(queueWWW.url);
         }
 
         NetworkManager.instance.StartCoroutine(WaitForRequest(www, queueWWW, queueWWW.successCB, queueWWW.apiFailureCB, queueWWW.layerFailureCB));
     }

We are waiting for request until we receive either that the request is done, there has been an error, or the request has timed out

     private IEnumerator<WWW> WaitForRequest(WWW www, Callback<ServerData> successCallback = null, Callback<string> apiFailureCallback = null, Callback<string> layerFailureCallback = null)
     {
         
         int startLaunchRequestTime = ServerTime.instance.getRealGameTime();
         while (!www.isDone && www.error == null && ((ServerTime.instance.getRealGameTime() - startLaunchRequestTime) < REQUEST_TIMEOUT))
         {
             yield return www;
         }
         
         LaunchCallback cb = new LaunchCallback();
         cb.www = www;
         cb.successCB = successCallback;
         cb.apiFailureCB = apiFailureCallback;
         cb.layerFailureCB = layerFailureCallback;
         
         if ((ServerTime.instance.getRealGameTime() - startLaunchRequestTime) >= REQUEST_TIMEOUT)
         {
             cb.timeOutError = true;
         }
         
         _launchCallbacks.Add(cb);
     }



II°) And then, in the update function, where we request the results for the launchcallback, handle the callbacks, and dispose the WWW object:

     public void update()
     {
         int len = _launchCallbacks.Count;
         List<LaunchCallback> callbackToDelete = new List<LaunchCallback>();
         for (int i=0; i<len; i++)
         {
             LaunchCallback cb = _launchCallbacks[i];
             RequestResult(cb.www, cb.hasTransaction,  cb.successCB, cb.apiFailureCB, cb.layerFailureCB, cb.timeOutError);
             callbackToDelete.Add(cb);
         }
         foreach(LaunchCallback call in callbackToDelete)
         {
             _launchCallbacks.Remove(call);
             call.dispose();
         }
     }


     private void RequestResult(WWW www, bool hasTransaction, Callback<ServerData> successCallback, Callback<string> apiFailureCallback , Callback<string> layerFailureCallback, bool timeOutError)
     {
         // handles success/ failure callbacks, not relevant here
         numberRequestInProgress --;
     }


III°) The LauncCcallback class is just a container for the WWW object and its callbacks:

     public class LaunchCallback
     {
         public Callback<ServerData> successCB;
         public Callback<string> apiFailureCB;
         public Callback<string> layerFailureCB;
         public WWW www;
         public bool hasTransaction;
         public bool timeOutError = false;
     
     
         public void dispose()
         {
             Debug.Log (" DISPOSING WWW object " + www.url );
             www.Dispose();
             successCB = null;
             apiFailureCB = null;
             layerFailureCB = null;
         }
     }
     




When I run the project on IOS, I get the debug output of DISPOSING WWW object " + www.url, but the leak instrument tells me that the issueRequest function has leaked several strings, dictionary etc etc, as if the WWW has not been disposed:

http://imgur.com/RWBbYwH

When I put a breakpoint in Xcode, we pass in the destroy function for every request:

 extern "C" void UnityDestroyWWWConnection(void* connection)
 {
     UnityWWWConnectionDelegate* delegate = (UnityWWWConnectionDelegate*)connection;
 
     [delegate cleanup];
     [delegate release];
 }

which calls this:

 - (void)cleanup
 {
     [_connection cancel];
     _connection = nil;
 
     [_data release];
     _data = nil;
 }


I really don't know what's going on, because I do pass in the dispose function of the launchcallback. Is there any references that could lead the WWW object to not be disposed?

PS: I am using unity 4.5.5 & monodevelop 4.0.1

Many thanks,

Down


UPDATE 1:

A little update:

After reviewing the code generated by unity in xcode and analyzing it: xcode tells me that there is a potential leak in the WWWConnection.mm, particulary here, the delegate.connection is never cleaned:

 extern "C" void* UnityStartWWWConnectionGet(void* udata, const void* headerDict, const char* url)
 {
     UnityWWWConnectionDelegate*    delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL:url udata:udata];
 
     NSMutableURLRequest* request =
         [UnityWWWConnectionDelegate newRequestForHTTPMethod:@"GET" url:delegate.url headers:(NSDictionary*)headerDict];
 
     delegate.connection = [NSURLConnection connectionWithRequest:request delegate:delegate];
     return delegate;
 }


the same occurs for POST methods:

 extern "C" void* UnityStartWWWConnectionPost(void* udata, const void* headerDict, const char* url, const void* data, unsigned length)
 {
     UnityWWWConnectionDelegate*    delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL:url udata:udata];
 
     NSMutableURLRequest* request =
         [UnityWWWConnectionDelegate newRequestForHTTPMethod:@"POST" url:delegate.url headers:(NSDictionary*)headerDict];
     [request setHTTPBody:[NSData dataWithBytes:data length:length]];
     [request setValue:[NSString stringWithFormat:@"%d", length] forHTTPHeaderField:@"Content-Length"];
 
     delegate.connection = [NSURLConnection connectionWithRequest:request delegate:delegate];
     return delegate;
 }



Which correlates nicely with what I see:

http://imgur.com/vIdeg54

I don't really know what to do, as it looks like its related to the unity code. Any help would be appreciated.


UPDATE 2

I have a similar leak when I call this function:

     private void OnEnable()
     {
      StartCoroutine(doGet(facebookURL, onGetFacebookAvatar));
     }

     public IEnumerator doGet(string url, Callback<WWW> callback = null) 
     {
         using (WWW www = new WWW(url))
         {
             yield return www;
             if (callback != null)
             {
                 callback(www);
                 callback = null;
             }
         }
     }

     private void onGetFacebookAvatar(WWW result)
     {
         if (!string.IsNullOrEmpty (result.error))
         {
             ErrorManager.instance.logWarning (ErrorManager.ERROR_FACEBOOK_API,  "Get facebook avatar failed : " + result.error, "AvatarBehaviour");
         }
         else
         {
             Texture2D texture = new Texture2D(frame.width-20, frame.height-20);
             if (!_facebookAvatar.ContainsKey(_userID))
             {
                 // Never use www.texture as it cause memory leak (WWW object is never released)
                 result.LoadImageIntoTexture(texture);
                 _facebookAvatar.Add(_userID, texture);
             }
             avatarTexture.mainTexture = texture;
             avatarTexture.width = frame.width - 20;
             avatarTexture.height = frame.height - 20;
             NGUITools.SetActiveSelf(avatarSprite.gameObject, false);
             NGUITools.SetActiveSelf(avatarTexture.gameObject, true);
         }
     }

see http://imgur.com/vIdeg54


UPDATE 3:

After some investigation, adding the line:

 [request release];

here

 extern "C" void* UnityStartWWWConnectionGet(void* udata, const void* headerDict, const char* url)
 {
     UnityWWWConnectionDelegate*    delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL:url udata:udata];
 
     NSMutableURLRequest* request =
         [UnityWWWConnectionDelegate newRequestForHTTPMethod:@"GET" url:delegate.url headers:(NSDictionary*)headerDict];
 
     delegate.connection = [NSURLConnection connectionWithRequest:request delegate:delegate];
     [request release];
     return delegate;
 }
 

Does helps, but still some leak, maybe because what's inside the request is not fully released (string and stuff), still investigating

Comment
Add comment · Show 5
10 |3000 characters needed characters left characters exceeded
â–¼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image smoggach · Oct 22, 2014 at 04:38 PM 0
Share
  while (!www.isDone && www.error == null && ((ServerTime.instance.getRealGameTime() - startLaunchRequestTime) < REQUEST_TI$$anonymous$$EOUT))
 {
 yield return www;
 }


If your www error is always null (a successful request) then how do you ever get out of this loop? Perhaps this coroutine is still running.

avatar image Bunny83 · Oct 22, 2014 at 05:00 PM 0
Share

@smoggach: He uses an && between the conditions. So if only one of them is not true it will ter$$anonymous$$ate the loop. However yielding the same www object multiple times is not a good idea at all. If this yield returns the request is done.

@htudrshtsrhtsthsht: You really should use

 yield return null;

in this while loop. I'm not familiar with objective C and iOS in general, but how have you actually found the "memory leak"? You are aware of the fact that the managed environment usually keeps some memory reserved for the future. Have you tried executing 20 or more requests in a row with a small delay? If you have a memory leak you should see a s$$anonymous$$dy increase for each call. If it just goes up during the first calls and then stays more or less constant it's not a leak.

avatar image htudrshtsrhtsthsht · Oct 23, 2014 at 07:32 AM 0
Share

Thanks for all the comments and inputs!

@smoggach: yes it is a AND between the statement so once the request is done or there is an error or we have timed out, we stop. Plus when I debug in xcode I can see that the WWW destroy function of unity is called, so the www object are destroyed, which means that we have exited the loop.

@Bunny83: I will try the suggestion to use "yield return null" ins$$anonymous$$d of yield return www, I am not that familiar with the yield statement.

@Bunny83: I found the leak using two things on macOSX: xcode, which has a very neat "Analyze" function that highlight "potential" leak, and Intruments, which is a set à tools available on macOSX that can track memory allocation, and object leaked (see screenshots, it is quite handy). Both of them points me to the WWWConnection class and particularly the UnityStartWWWConnectionGet/POST methods.

(more precisely, I was tracking allocation of the HTTPParser object in Instruments, and it is very noticeable that when they are created, severals are not destroyed (ram usage was always increasing for this object, even after 50+ requests)).

Also I had a s$$anonymous$$dy increase of ram usage (not that much but still noticeable) while all I was doing was http request every $$anonymous$$utes.

avatar image htudrshtsrhtsthsht · Oct 23, 2014 at 03:35 PM 0
Share

After investigating a bit more, I am 100% sure that there is a problem in the two aforementioned methods. I have counted the number of request sent against the number of request disposed, and the difference is always 0, which means that I always call the dispose method on each and every WWW object I use, but when I profile it in Instruments, I am almost sure that there is a leak, because the allocated memory for NFSString and HTTPParser does constantly increase, AND the call stack ALWAYS contains one of the 2 aforementioned methods. I will try to create a small test case to see how to reproduce it.

avatar image htudrshtsrhtsthsht · Oct 24, 2014 at 03:38 PM 0
Share

Alright I have a test case built right here:

link text

You just need to provide your server URL in the AnalyticsLoggerImpl class. I am going to report the bug (when the issue tracker will let me do so)

unityleakwwwtestcase.zip (214.1 kB)

1 Reply

· Add your reply
  • Sort: 
avatar image
0
Best Answer

Answer by htudrshtsrhtsthsht · Oct 27, 2014 at 08:40 AM

All right, it seems that I was not alone:

http://issuetracker.unity3d.com/issues/memory-leak-in-www-class-on-ios

Looks like it's solved in unity 4.5.6, just have to wait for the release. Anyway, thx for the help

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
â–¼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image DeveshPandey · Jun 26, 2017 at 03:16 PM 0
Share

See this post. http://unitydevelopers.blogspot.in/2017/06/large-file-downloader-for-unity3d.html

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

5 People are following this question.

avatar image avatar image avatar image avatar image avatar image

Related Questions

@ makes problem in ios 1 Answer

Memory management theory question 1 Answer

Restoring In App Purchases for iOS using Unity 1 Answer

Unity WWW Warning Message in Xcode 1 Answer

Everyplay for Unity3D - Error for building on iOS 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges