{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/corefx/issues/5205","labels_url":"https://api.github.com/repos/dotnet/corefx/issues/5205/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/corefx/issues/5205/comments","events_url":"https://api.github.com/repos/dotnet/corefx/issues/5205/events","html_url":"https://github.com/dotnet/corefx/issues/5205","id":125263557,"number":5205,"title":"API review: During shutdown, revisit finalization and provide a way to clean up resources","user":{"login":"kouvel","id":13499639,"avatar_url":"https://avatars.githubusercontent.com/u/13499639?v=3","gravatar_id":"","url":"https://api.github.com/users/kouvel","html_url":"https://github.com/kouvel","followers_url":"https://api.github.com/users/kouvel/followers","following_url":"https://api.github.com/users/kouvel/following{/other_user}","gists_url":"https://api.github.com/users/kouvel/gists{/gist_id}","starred_url":"https://api.github.com/users/kouvel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kouvel/subscriptions","organizations_url":"https://api.github.com/users/kouvel/orgs","repos_url":"https://api.github.com/users/kouvel/repos","events_url":"https://api.github.com/users/kouvel/events{/privacy}","received_events_url":"https://api.github.com/users/kouvel/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/dotnet/corefx/labels/api-ready-for-review","name":"api-ready-for-review","color":"5319e7"},{"url":"https://api.github.com/repos/dotnet/corefx/labels/System.Runtime","name":"System.Runtime","color":"d4c5f9"}],"state":"open","locked":false,"assignee":{"login":"kouvel","id":13499639,"avatar_url":"https://avatars.githubusercontent.com/u/13499639?v=3","gravatar_id":"","url":"https://api.github.com/users/kouvel","html_url":"https://github.com/kouvel","followers_url":"https://api.github.com/users/kouvel/followers","following_url":"https://api.github.com/users/kouvel/following{/other_user}","gists_url":"https://api.github.com/users/kouvel/gists{/gist_id}","starred_url":"https://api.github.com/users/kouvel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kouvel/subscriptions","organizations_url":"https://api.github.com/users/kouvel/orgs","repos_url":"https://api.github.com/users/kouvel/repos","events_url":"https://api.github.com/users/kouvel/events{/privacy}","received_events_url":"https://api.github.com/users/kouvel/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/dotnet/corefx/milestones/4","html_url":"https://github.com/dotnet/corefx/milestones/1.0.0-rtm","labels_url":"https://api.github.com/repos/dotnet/corefx/milestones/4/labels","id":1248719,"number":4,"title":"1.0.0-rtm","description":"Q1 2016 (https://github.com/aspnet/Home/wiki/Roadmap)","creator":{"login":"joshfree","id":1302850,"avatar_url":"https://avatars.githubusercontent.com/u/1302850?v=3","gravatar_id":"","url":"https://api.github.com/users/joshfree","html_url":"https://github.com/joshfree","followers_url":"https://api.github.com/users/joshfree/followers","following_url":"https://api.github.com/users/joshfree/following{/other_user}","gists_url":"https://api.github.com/users/joshfree/gists{/gist_id}","starred_url":"https://api.github.com/users/joshfree/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joshfree/subscriptions","organizations_url":"https://api.github.com/users/joshfree/orgs","repos_url":"https://api.github.com/users/joshfree/repos","events_url":"https://api.github.com/users/joshfree/events{/privacy}","received_events_url":"https://api.github.com/users/joshfree/received_events","type":"User","site_admin":false},"open_issues":256,"closed_issues":127,"state":"open","created_at":"2015-08-10T18:04:00Z","updated_at":"2016-01-08T22:49:14Z","due_on":"2016-03-31T07:00:00Z","closed_at":null},"comments":8,"created_at":"2016-01-06T21:08:29Z","updated_at":"2016-01-08T22:50:54Z","closed_at":null,"body":"Running finalizers during shutdown is currently unreliable when there are still background threads running. This is a proposal to fix that and provide a way to clean up resources on shutdown in a reliable way.\r\n\r\n## Issues observed on shutdown\r\n\r\n- Currently, a best-effort attempt is made to run finalizers for all finalizable objects during shutdown, including live objects\r\n- In order to finalize live objects, threads must be blocked, since objects that are still live cannot be used during or after finalization. Later, threads are terminated without running any more user code.\r\n- Running user code in finalizers after blocking other threads is unreliable, as those threads may be blocked at an inopportune point, and may cause finalizers to block indefinitely\r\n  - For instance, if a thread is blocked during GC, and a finalizer tries to allocate something, it may block waiting for GC to complete, which will never happen. The finalizer itself may not even allocate anything, but even just jitting the finalizer method will trigger allocation.\r\n- Effectively, the best-effort attempt to run finalizers for all finalizable objects is not reliable. If we choose to not block threads before running finalizers, then finalizers cannot be run for live objects.\r\n\r\n## Proposal\r\n\r\n- Don't block threads before running finalizers\r\n- Run finalizers only for dead objects\r\n- Don't do a GC on shutdown to maximize the number of dead objects that can be finalized\r\n  - Doing a GC on shutdown can guarantee that objects that are deterministically dead by the time of shutdown will be finalized. However, such objects should also be deterministically disposed before shutdown. For cases that require this, the user can trigger a GC explicitly before shutdown.\r\n  - When there are background threads that are still running, there would be no guarantee on how many objects will be finalized anyway\r\n- Provide a public AssemblyLoadContext.Unload event that is raised during shutdown. Registered event handlers can clean up global resources.\r\n  - The event would be raised on the finalizer thread, before doing the final round of calling finalizers (at the same point where AppDomain.ProcessExit is raised)\r\n  - Since other threads are not blocked before this, and may continue to run for a short period after the event is raised, event handlers may need to handle concurrency, and safeguard from using cleaned up resources from other threads\r\n  - The timeout on waiting for finalizers to complete on shutdown was removed in CoreCLR some time back. In favor of treating blocking issues as program errors, no timeout will be used for this event either.\r\n\r\n### Behavioral change\r\n\r\n```c#\r\npublic static void Main()\r\n{\r\n    var obj = new MyFinalizable();\r\n}\r\n\r\nprivate class MyFinalizable\r\n{\r\n    ~MyFinalizable()\r\n    {\r\n        Console.WriteLine(\"~MyFinalizable\");\r\n    }\r\n}\r\n```\r\n\r\nPrevious output:\r\n~MyFinalizable\r\n\r\nTypical output with the proposal above (running the finalizer is not guaranteed, but may run if a GC is triggered):\r\n(empty)\r\n\r\n## Proposed API\r\n\r\n```c#\r\nnamespace System.Runtime.Loader\r\n{\r\n    public abstract class AssemblyLoadContext\r\n    {\r\n        public event EventHandler Unload;\r\n    }\r\n}\r\n```\r\n\r\n### Example\r\n\r\n```c#\r\npublic class Logger\r\n{\r\n    private static readonly object s_lock = new object();\r\n    private static bool s_isClosed = false;\r\n\r\n    static Logger()\r\n    {\r\n        AssemblyLoadContext.Default.Unload += OnAssemblyLoadContextUnload;\r\n\r\n        // Create log file based on configuration\r\n    }\r\n\r\n    private static void OnAssemblyLoadContextUnload(object sender, EventArgs e)\r\n    {\r\n        // This may be called concurrently with WriteLine\r\n        Close();\r\n    }\r\n\r\n    private static void Close()\r\n    {\r\n        lock (s_lock)\r\n        {\r\n            if (s_isClosed)\r\n                return;\r\n            s_isClosed = true;\r\n\r\n            // Write remaining in-memory log messages to log file and close log file\r\n        }\r\n    }\r\n\r\n    public static void WriteLine(string message)\r\n    {\r\n        lock (s_lock)\r\n        {\r\n            if (s_isClosed)\r\n                return;\r\n\r\n            // Save log message in memory, if buffer is full, write messages to log file\r\n        }\r\n    }\r\n}\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/dotnet/corefx/issues/comments/170149539","html_url":"https://github.com/dotnet/corefx/issues/5205#issuecomment-170149539","issue_url":"https://api.github.com/repos/dotnet/corefx/issues/5205","id":170149539,"user":{"login":"kouvel","id":13499639,"avatar_url":"https://avatars.githubusercontent.com/u/13499639?v=3","gravatar_id":"","url":"https://api.github.com/users/kouvel","html_url":"https://github.com/kouvel","followers_url":"https://api.github.com/users/kouvel/followers","following_url":"https://api.github.com/users/kouvel/following{/other_user}","gists_url":"https://api.github.com/users/kouvel/gists{/gist_id}","starred_url":"https://api.github.com/users/kouvel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kouvel/subscriptions","organizations_url":"https://api.github.com/users/kouvel/orgs","repos_url":"https://api.github.com/users/kouvel/repos","events_url":"https://api.github.com/users/kouvel/events{/privacy}","received_events_url":"https://api.github.com/users/kouvel/received_events","type":"User","site_admin":false},"created_at":"2016-01-08T22:50:54Z","updated_at":"2016-01-08T22:50:54Z","body":"Made a couple of changes:\r\n- Added notes about there being no timeouts on running finalizers (already the case), and on processing the new event\r\n- Changed the static ProcessExit event to an instance Unload event. For the time being, it will be raised at the same place as AppDomain.ProcessExit. Once AssemblyLoadContext unloading is implemented, it will move to a more appropriate spot."}}