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*;*
-
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 successCallback, Callback apiFailureCallback , Callback 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 successCB;*
-
public Callback apiFailureCB;*
-
public Callback 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:
Imgur: The magic of the Internet
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:
Imgur: The magic of the Internet
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 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 Imgur: The magic of the Internet
---------------------------------------------------------------------------------------------------------------
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
while (!www.isDone && www.error == null && ((ServerTime.instance.getRealGameTime() - startLaunchRequestTime) < REQUEST_TIMEOUT)) { 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.
– smoggachAlright I have a test case built right here: [34234-unityleakwwwtestcase.zip|34234] 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)
– htudrshtsrhtsthsht