1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 Trim the heap size by periodically force the GC to collect unused memory, free
7 it and then tell malloc to further free it back to the OS.
8 */
9 module my.gc.memfree;
10 
11 import std.concurrency : send, spawn, receiveTimeout, Tid;
12 
13 import my.gc.refc;
14 import my.libc;
15 
16 /// Returns: a started instance of MemFree.
17 MemFree memFree() @safe {
18     return MemFree(true);
19 }
20 
21 /** Reduces the used memory by the GC and free the heap to the OS.
22  *
23  * To avoid calling this too often the struct have a timer to ensure it is
24  * callled at most ones every minute.
25  *
26  * TODO: maybe add functionality to call it more often when above e.g. 50% memory usage?
27  */
28 struct MemFree {
29     private static struct Data {
30         bool isRunning;
31         Tid bg;
32     }
33 
34     private RefCounted!Data data;
35 
36     this(bool startNow) @safe {
37         if (startNow)
38             start;
39     }
40 
41     ~this() @trusted {
42         if (data.empty || !data.get.isRunning)
43             return;
44 
45         scope (exit)
46             data.get.isRunning = false;
47         send(data.get.bg, Msg.stop);
48     }
49 
50     /** Start a background thread to do the work.
51      *
52      * It terminates when the destructor is called.
53      */
54     void start() @trusted {
55         data = Data(true, spawn(&tick));
56     }
57 
58 }
59 
60 private:
61 
62 enum Msg {
63     stop,
64 }
65 
66 void tick() nothrow {
67     import core.time : dur;
68     import core.memory : GC;
69 
70     const tickInterval = 1.dur!"minutes";
71 
72     bool running = true;
73     while (running) {
74         try {
75             receiveTimeout(tickInterval, (Msg x) { running = false; });
76         } catch (Exception e) {
77             running = false;
78         }
79 
80         GC.collect;
81         GC.minimize;
82         malloc_trim(0);
83     }
84 }