{ "id": "133990", "key": "TIMOB-17502", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [ { "id": "16593", "description": "Release 4.0.0", "name": "Release 4.0.0", "archived": false, "released": true, "releaseDate": "2015-05-21" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2015-01-07T16:32:50.000+0000", "created": "2014-07-31T18:57:59.000+0000", "priority": { "name": "Medium", "id": "3" }, "labels": [ "TCSupport" ], "versions": [ { "id": "15422", "description": "Release 3.3.0", "name": "Release 3.3.0", "archived": false, "released": true, "releaseDate": "2014-07-16" } ], "issuelinks": [ { "id": "48412", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "inwardIssue": { "id": "140651", "key": "TIMOB-19333", "fields": { "summary": "iOS: URLSession Module not working properly if system terminates the app during download", "status": { "description": "The issue is open and ready for the assignee to start work on it.", "name": "Open", "id": "1", "statusCategory": { "id": 2, "key": "new", "colorName": "blue-gray", "name": "To Do" } }, "priority": { "name": "None", "id": "6" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } }, { "id": "48410", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "inwardIssue": { "id": "149729", "key": "TIMOB-19334", "fields": { "summary": "iOS: URLSession - large file download in the background - some events are not fired and other issues.", "status": { "description": "The issue is open and ready for the assignee to start work on it.", "name": "Open", "id": "1", "statusCategory": { "id": 2, "key": "new", "colorName": "blue-gray", "name": "To Do" } }, "priority": { "name": "None", "id": "6" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } } ], "assignee": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "updated": "2015-07-16T18:47:41.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10206", "name": "iOS", "description": "iOS Platform" } ], "description": "h4. Problem Description\r\ndownloadcompleted returns e.data as a blob in physical memory!! If the file size is large, for example, over 1GB, it could cause low memory crash. Plus, using file.write(e.data) in this case takes too much time to run in the background. iOS only allows 30 seconds. Chances are the app will be either crashed or suspended before the file is saved.\r\n\r\nI suggest: upon downloadcompleted move the file to a temp directory and return the path instead of a blob. \r\n\r\n\r\nh4. TESTING ENVIRONMENT\r\n\r\nCLI version 3.3.0, \r\nTitanium SDK version 3.3.0.GA\r\niOS7\r\n\r\nh4. CODE SAMPLE\r\n\r\nh3. app.js\r\n{code}\r\n// Require in the urlSession module\r\nvar urlSession = require(\"com.appcelerator.urlSession\");\r\nvar session;\r\n\r\n// App UI\r\nvar win = Ti.UI.createWindow({backgroundColor:\"white\"});\r\nvar progress = Ti.UI.createProgressBar({\r\n width: 200,\r\n height: 50,\r\n min: 0,\r\n max: 1,\r\n value: 0,\r\n style: Ti.UI.iPhone.ProgressBarStyle.PLAIN,\r\n top: 10,\r\n message: 'Downloading image URL',\r\n font: { fontSize: 12, fontWeight: 'bold'},\r\n color: '#888'\r\n});\r\nwin.add(progress);\r\nvar imageView = Ti.UI.createImageView({\r\n top:150,\r\n height:300,\r\n width:200\r\n });\r\nwin.add(imageView);\r\n\r\nvar button = Ti.UI.createButton({\r\n title:'Download Image (url)',\r\n height:40,\r\n width:200,\r\n top:70\r\n});\r\n\r\nbutton.addEventListener('click', function()\r\n{\r\n // Create a session configuration\r\n // The string parameter is an arbitrary string used to identify the session in events\r\n var sessionConfig = urlSession.createURLSessionBackgroundConfiguration(\"com.appcelerator.test\");\r\n // Create a session\r\n session = urlSession.createURLSession(sessionConfig);\r\n // Create a background download task to get the asset with the URL\r\n urlSession.backgroundDownloadTaskWithURL(session,\"https://raw.github.com/appcelerator-developer-relations/KitchenSink/master/Resources/images/dog@2x~iphone.jpg\");\r\n\r\n progress.show();\r\n});\r\n\r\nwin.add(button);\r\nwin.open();\r\n \r\n// Monitor this event to receive updates on the progress of the download\r\nTi.App.iOS.addEventListener(\"downloadprogress\", function(e) {\r\n // Update the progress indicator\r\n progress.value = (e.totalBytesWritten/e.totalBytesExpectedToWrite);\r\n});\r\n\r\n\r\n// Monitor this event to know when the download completes\r\nTi.App.iOS.addEventListener(\"downloadcompleted\", function(e) {\r\n Ti.API.info(\"download completed \" + JSON.stringify(e));\r\n \r\n // Update the image\r\n imageView.image = e.data;\r\n \r\n // Invalidate the session and cancel current session tasks\r\n urlSession.invalidateAndCancel(session);\r\n\r\n // Notify the user the download is complete if the application is in the background\r\n Ti.App.iOS.scheduleLocalNotification({\r\n alertBody:\"Download complete!\",\r\n date:new Date().getTime() \r\n });\r\n progress.hide();\r\n});\r\n\r\n\r\n// Monitor this event to know when all session tasks have completed\r\nTi.App.iOS.addEventListener('sessioncompleted', function(e) {\r\n Ti.API.info(\"sessioncompleted \" + JSON.stringify(e));\r\n if (e.success) {\r\n alert(\"Downloads completed successfully.\");\r\n }\r\n});\r\n{code}\r\n\r\nh4. STEP TO REPRODUCE\r\n\r\n- Create a new Project\r\n- Double-click the Project's \"tiapp.xml\" file to open it in the Overview tab.\r\n- In the Modules section, click the Add button (green plus sign) to open the Mobile Modules dialog.\r\n- Locate and select \"com.appcelerator.urlSession\". This module is part of the Titanium SDK since Release 3.2.0\r\n- Click OK.\r\n- Copy the code segment above to the project \"app.js\" file\r\n- Run the Project \r\n\r\nh4. OBSERVED RESULT\r\n\r\nIf the URL is changed to download a large file, progress bar would not response and the app will crash after some time.\r\n\r\n", "attachment": [], "flagged": false, "summary": "iOS: URLSession Module returns blob instead of file location - causing low memory crash", "creator": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "subtasks": [], "reporter": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "environment": "SDK 3.3.0 iOS7", "closedSprints": [ { "id": 291, "state": "closed", "name": "2015 Sprint 01 SDK", "startDate": "2015-01-03T01:00:00.000Z", "endDate": "2015-01-17T01:00:00.000Z", "completeDate": "2015-01-19T17:24:40.828Z", "originBoardId": 114 }, { "id": 272, "state": "closed", "name": "2014 Sprint 25 SDK", "startDate": "2014-12-08T22:49:31.264Z", "endDate": "2014-12-22T01:00:00.000Z", "completeDate": "2015-01-05T18:18:28.523Z", "originBoardId": 114 } ], "comment": { "comments": [ { "id": "318615", "author": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "body": "Thanks for your report. The Platform team will set the priority on this issue. ", "updateAuthor": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "created": "2014-08-14T07:53:12.000+0000", "updated": "2014-08-14T07:53:12.000+0000" }, { "id": "334460", "author": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "body": "When will you set a priority on this issue? This is important to me. If you can release your URLSession module's source code, I can implement this myself. Thanks.", "updateAuthor": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-12-03T01:51:39.000+0000", "updated": "2014-12-03T01:51:39.000+0000" }, { "id": "334625", "author": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~shawnlan] the module is here. We would welcome a PR to fix this issue: https://github.com/appcelerator-modules/ti.urlsession", "updateAuthor": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-12-03T20:17:00.000+0000", "updated": "2014-12-03T20:17:00.000+0000" }, { "id": "334629", "author": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "body": "I don't see any source file other than this one: https://github.com/appcelerator-modules/ti.urlsession/blob/master/ios/js/app.hjs\r\n\r\nThat's the only source file for this module?", "updateAuthor": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-12-03T20:30:15.000+0000", "updated": "2014-12-03T20:30:15.000+0000" }, { "id": "334676", "author": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Yes, since this was built using Hyperloop. It may be relatively difficult to figure out how to update it ATM, but there are instructions on the repo. ", "updateAuthor": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-12-03T22:48:34.000+0000", "updated": "2014-12-03T22:48:34.000+0000" }, { "id": "334709", "author": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Actually, to fix this issue, I think we have to mainly deal with the iOS application-level events:\r\n\r\nTitanium.App.iOS.backgroundtransfer\r\nTitanium.App.iOS.downloadprogress\r\nTitanium.App.iOS.downloadcompleted - especially this one\r\nTitanium.App.iOS.sessioncompleted\r\nTitanium.App.iOS.sessioneventscompleted\r\n\r\nthey are in the Titanium SDK, right? I'll wait for you guys to fix this. Thanks.", "updateAuthor": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-12-04T00:17:27.000+0000", "updated": "2014-12-04T00:17:27.000+0000" }, { "id": "334733", "author": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Here it is: https://github.com/appcelerator/titanium_mobile/blob/afc36d4f421424dc1ef6d618201f43616bb7857b/iphone/Classes/TiApp.m (line 612)\r\n\r\nInstead of loading the file into a blob, simply move the file into a temporary location and return the path. Should be simple.", "updateAuthor": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-12-04T01:47:23.000+0000", "updated": "2014-12-04T01:47:23.000+0000" }, { "id": "337511", "author": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "body": "URL Session Module fixed to be able to download file sizes larger than 1Gb with no low memory crash. Not required to return file location. If necessary, it is possible to obtain file via blob.getFile();\r\n\r\nupdated test code:\r\n{code:title=app.js|borderStyle=solid}\r\n// Require in the urlSession module\r\nvar urlSession = require(\"com.appcelerator.urlSession\");\r\nvar session;\r\n \r\n// App UI\r\nvar win = Ti.UI.createWindow({backgroundColor:\"white\"});\r\nvar progress = Ti.UI.createProgressBar({\r\n width: 200,\r\n height: 50,\r\n min: 0,\r\n max: 1,\r\n value: 0,\r\n style: Ti.UI.iPhone.ProgressBarStyle.PLAIN,\r\n top: 10,\r\n message: 'Downloading image URL',\r\n font: { fontSize: 12, fontWeight: 'bold'},\r\n color: '#888'\r\n});\r\nwin.add(progress);\r\nvar imageView = Ti.UI.createImageView({\r\n top:150,\r\n height:300,\r\n width:200\r\n });\r\nwin.add(imageView);\r\n \r\nvar button = Ti.UI.createButton({\r\n title:'Download Image (url)',\r\n height:40,\r\n width:200,\r\n top:70\r\n});\r\n \r\nbutton.addEventListener('click', function()\r\n{\r\n // Check if the device is running iOS 8 or later, before registering for local notifications\r\n if (Ti.Platform.name == \"iPhone OS\" && parseInt(Ti.Platform.version.split(\".\")[0]) >= 8) {\r\n Ti.App.iOS.registerUserNotificationSettings({\r\n types: [\r\n Ti.App.iOS.USER_NOTIFICATION_TYPE_ALERT,\r\n Ti.App.iOS.USER_NOTIFICATION_TYPE_SOUND,\r\n Ti.App.iOS.USER_NOTIFICATION_TYPE_BADGE\r\n ]\r\n });\r\n }\r\n // Create a session configuration\r\n // The string parameter is an arbitrary string used to identify the session in events\r\n var sessionConfig = urlSession.createURLSessionBackgroundConfiguration(\"com.appcelerator.test\");\r\n // Create a session\r\n session = urlSession.createURLSession(sessionConfig);\r\n // Create a background download task to get the asset with the URL\r\n //urlSession.backgroundDownloadTaskWithURL(session,\"https://raw.github.com/appcelerator-developer-relations/KitchenSink/master/Resources/images/dog@2x~iphone.jpg\"); //small file\r\n //urlSession.backgroundDownloadTaskWithURL(session,\"http://imgsrc.hubblesite.org/hu/db/images/hs-2004-07-a-full_jpg.jpg\"); //medium sized file\r\n urlSession.backgroundDownloadTaskWithURL(session,\"http://www.wswd.net/testdownloadfiles/1GB.zip\"); //huge file\r\n progress.show();\r\n});\r\n \r\nwin.add(button);\r\nwin.open();\r\n \r\n// Monitor this event to receive updates on the progress of the download\r\nTi.App.iOS.addEventListener(\"downloadprogress\", function(e) {\r\n // Update the progress indicator\r\n progress.value = (e.totalBytesWritten/e.totalBytesExpectedToWrite);\r\n});\r\n \r\n \r\n// Monitor this event to know when the download completes\r\nTi.App.iOS.addEventListener(\"downloadcompleted\", function(e) {\r\n Ti.API.info(\"download completed \" + JSON.stringify(e));\r\n var cacheFile = e.data.getFile();\r\n var downloadedFile = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, cacheFile.getName());\r\n downloadedFile.write(e.data); // copy and save from cache\r\n Ti.API.info(\"downloadedFile: \" + downloadedFile.getNativePath());\r\n // set new Image\r\n //imageView.image = e.data;\r\n // Invalidate the session and cancel current session tasks\r\n urlSession.invalidateAndCancel(session);\r\n \r\n // Notify the user the download is complete if the application is in the background\r\n var notification = Ti.App.iOS.scheduleLocalNotification({\r\n alertBody: \"Download completed!\",\r\n badge: 1,\r\n sound: \"/alert.wav\",\r\n date: new Date(new Date().getTime())\r\n });\r\n\r\n progress.hide();\r\n});\r\n \r\n \r\n// Monitor this event to know when all session tasks have completed\r\nTi.App.iOS.addEventListener('sessioncompleted', function(e) {\r\n Ti.API.info(\"sessioncompleted \" + JSON.stringify(e));\r\n if (e.success) {\r\n alert(\"Downloads completed successfully.\");\r\n }\r\n});\r\n{code}\r\n\r\n*Note: the link downloaded is 1GB in size in the test code above. Replace with commented out lines of code linked to smaller files if necessary.*\r\n", "updateAuthor": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-12-26T08:58:21.000+0000", "updated": "2014-12-30T16:47:14.000+0000" }, { "id": "337512", "author": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "body": "PR Here: https://github.com/appcelerator/titanium_mobile/pull/6527", "updateAuthor": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-12-26T09:17:05.000+0000", "updated": "2014-12-26T09:17:05.000+0000" }, { "id": "337687", "author": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Your solution doesn't require returning file location, but it also doesn't load the blob into memory. Right? If it loads blob into memory like before, then there would be memory-low issue.", "updateAuthor": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2014-12-31T01:34:32.000+0000", "updated": "2014-12-31T01:34:32.000+0000" }, { "id": "337688", "author": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "body": "[~shawnlan], yes, my solution doesn't load blob into memory.\r\nThe fix is instead of allocating memory, Save a copy of the file in the temporary directory, and the returned blob actually just reference that file. \r\n", "updateAuthor": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2014-12-31T02:12:53.000+0000", "updated": "2014-12-31T02:12:53.000+0000" }, { "id": "338177", "author": { "name": "tstatler", "key": "tstatler", "displayName": "Tim Statler", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Hi [~cng], does this change require a doc update?", "updateAuthor": { "name": "tstatler", "key": "tstatler", "displayName": "Tim Statler", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2015-01-07T17:59:32.000+0000", "updated": "2015-01-07T17:59:32.000+0000" }, { "id": "338313", "author": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Hi [~tstatler], No doc update required since the parameters remain the same. \r\nHowever it'll be good to put a note somewhere about downloading big files it's advisable to make a copy as described in the above sample code, since the returned blob references a file location that's only temporary for that app instance.", "updateAuthor": { "name": "cng", "key": "cng", "displayName": "Chee Kiat Ng", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2015-01-08T02:06:16.000+0000", "updated": "2015-01-08T02:06:16.000+0000" }, { "id": "339571", "author": { "name": "eharris", "key": "eharris", "displayName": "Ewan Harris", "active": true, "timeZone": "Europe/Dublin" }, "body": "Verified fix on:\r\n\r\nMac OSX 10.10.1\r\nAppcelerator Studio, build: 3.4.1.201410281743\r\nTitanium SDK build: 3.6.0.v20150116070927\r\nTitanium CLI, build: 3.4.1\r\nAlloy: 1.5.1\r\nXcode 6.1.1\r\niPhone 6 Plus (8.1.1), iPad Air 2 (8.2b4)\r\n\r\nUsing the code in Kiats comment, ran on both devices and downloaded each of the files, the app no longer crashes with low memory. Observed memory in Xcode Instruments during the download as well.\r\n\r\nClosing ticket", "updateAuthor": { "name": "eharris", "key": "eharris", "displayName": "Ewan Harris", "active": true, "timeZone": "Europe/Dublin" }, "created": "2015-01-17T01:16:57.000+0000", "updated": "2015-01-17T01:16:57.000+0000" }, { "id": "349772", "author": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Will this fix be available with SDK 3.6 or SDK 4.0?", "updateAuthor": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2015-04-16T00:36:08.000+0000", "updated": "2015-04-16T00:36:08.000+0000" }, { "id": "349840", "author": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "body": "[~shawnlan] 4.0, as the fix version says.", "updateAuthor": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2015-04-16T15:32:13.000+0000", "updated": "2015-04-16T15:32:13.000+0000" }, { "id": "357808", "author": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Today I found a problem again with large file download. To re-produce, download a large file > 1GB, press \"Home\" to make the app go to the background. Once the download finishes, only \"backgroundtransfer\" is fired. The other events are not: sessioneventscompleted, downloadcompleted, etc.\r\n\r\nWithout downloadcompleted, the file can't be saved. Without sessioneventscompleted, the app won't call endBackgroundHandler, and it will run over 30 seconds, eventually being terminated by the system.\r\n\r\nWith a medium size download (2-300MB) in the background, calling endBackgroundHandler when sessioneventscompleted fired throws the following error, and the callback is failed to complete.\r\n{code}\r\n[WARN] : ing: Application delegate received call to -application:handleEventsForBackgroundURLSession:completionHandler: but the completion handler was never called.\r\n[WARN] : assertion failed: 12H143: libxpc.dylib + 51923 [7FF71721-5624-3DDE-9615-146A4D8B7197]: 0x7d\r\n{code}", "updateAuthor": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2015-07-16T02:13:48.000+0000", "updated": "2015-07-16T02:13:48.000+0000" }, { "id": "357852", "author": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "updateAuthor": { "name": "shawnlan", "key": "shawnlan", "displayName": "Shawn Lan", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2015-07-16T18:47:41.000+0000", "updated": "2015-07-16T18:47:41.000+0000" } ], "maxResults": 20, "total": 20, "startAt": 0 } } }