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