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.isRunning)
43 return;
44
45 scope (exit)
46 data.isRunning = false;
47 send(data.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 }