1 /**
2  * Supporting of dynamic configuration of libpq
3  */
4 module dpq2.dynloader;
5 
6 version(Dpq2_Dynamic):
7 
8 import dpq2.connection: Connection;
9 import core.sync.mutex: Mutex;
10 import dpq2.exception: Dpq2Exception;
11 
12 class ConnectionFactory
13 {
14     private __gshared Mutex mutex;
15     private __gshared bool instanced;
16     private ReferenceCounter cnt;
17 
18     shared static this()
19     {
20         mutex = new Mutex();
21     }
22 
23     //mixin ctorBody; //FIXME: https://issues.dlang.org/show_bug.cgi?id=22627
24     immutable mixin ctorBody;
25 
26     // If ctor throws dtor will be called. This is behaviour of current D design.
27     // https://issues.dlang.org/show_bug.cgi?id=704
28     private immutable bool isSucessfulConstructed;
29 
30     private mixin template ctorBody()
31     {
32         this() { this(""); }
33 
34         /**
35             Params:
36                 path =  A string containing one or more comma-separated shared
37                         library names.
38         */
39         this(string path)
40         {
41             import std.exception: enforce;
42 
43             mutex.lock();
44             scope(success) instanced = true;
45             scope(exit) mutex.unlock();
46 
47             enforce!Dpq2Exception(!instanced, "Already instanced");
48 
49             cnt = ReferenceCounter(path);
50             assert(ReferenceCounter.instances == 1);
51 
52             isSucessfulConstructed = true;
53         }
54     }
55 
56     ~this()
57     {
58         mutex.lock();
59         scope(exit) mutex.unlock();
60 
61         if(isSucessfulConstructed)
62         {
63             assert(instanced);
64 
65             cnt.__custom_dtor();
66         }
67 
68         instanced = false;
69     }
70 
71     /// This method is need to forbid attempts to create connection without properly loaded libpq
72     /// Accepts same parameters as Connection ctors in static configuration
73     Connection createConnection(T...)(T args) const
74     {
75         mutex.lock();
76         scope(exit) mutex.unlock();
77 
78         assert(instanced);
79 
80         return new Connection(args);
81     }
82 
83     void connStringCheck(string connString) const
84     {
85         mutex.lock();
86         scope(exit) mutex.unlock();
87 
88         assert(instanced);
89 
90         import dpq2.connection;
91 
92         _connStringCheck(connString);
93     }
94 }
95 
96 package struct ReferenceCounter
97 {
98     import core.atomic;
99     import derelict.pq.pq: DerelictPQ;
100     debug import std.experimental.logger;
101     import std.stdio: writeln;
102 
103     debug(dpq2_verbose) invariant()
104     {
105         mutex.lock();
106         scope(exit) mutex.unlock();
107 
108         import std.stdio;
109         writeln("Instances ", instances);
110     }
111 
112     private __gshared Mutex mutex;
113     private __gshared ptrdiff_t instances;
114 
115     shared static this()
116     {
117         mutex = new Mutex();
118     }
119 
120     this() @disable;
121     this(this) @disable;
122 
123     /// Used only by connection factory
124     this(string path)
125     {
126         mutex.lock();
127         scope(exit) mutex.unlock();
128 
129         assert(instances == 0);
130 
131         debug trace("DerelictPQ loading...");
132         DerelictPQ.load(path);
133         debug trace("...DerelictPQ loading finished");
134 
135         instances++;
136     }
137 
138     /// Used by all other objects
139     this(bool)
140     {
141         mutex.lock();
142         scope(exit) mutex.unlock();
143 
144         assert(instances > 0);
145 
146         instances++;
147     }
148 
149     // TODO: here is must be a destructor, but:
150     // "This is bug or not? (immutable class containing struct with dtor)"
151     // https://forum.dlang.org/post/spim8c$108b$1@digitalmars.com
152     // https://issues.dlang.org/show_bug.cgi?id=13628
153     void __custom_dtor() const
154     {
155         mutex.lock();
156         scope(exit) mutex.unlock();
157 
158         assert(instances > 0);
159 
160         instances--;
161 
162         if(instances == 0)
163         {
164             //TODO: replace writeln by trace?
165             debug trace("DerelictPQ unloading...");
166             DerelictPQ.unload();
167             debug trace("...DerelictPQ unloading finished");
168         }
169     }
170 }
171 
172 version (integration_tests):
173 
174 void _integration_test()
175 {
176     import std.exception : assertThrown;
177 
178     // Some testing:
179     {
180         auto f = new immutable ConnectionFactory();
181         assert(ConnectionFactory.instanced);
182         assert(ReferenceCounter.instances == 1);
183         f.destroy;
184     }
185 
186     assert(ConnectionFactory.instanced == false);
187     assert(ReferenceCounter.instances == 0);
188 
189     {
190         auto f = new immutable ConnectionFactory();
191 
192         // Only one instance of ConnectionFactory is allowed
193         assertThrown!Dpq2Exception(new immutable ConnectionFactory());
194 
195         assert(ConnectionFactory.instanced);
196         assert(ReferenceCounter.instances == 1);
197 
198         f.destroy;
199     }
200 
201     assert(!ConnectionFactory.instanced);
202     assert(ReferenceCounter.instances == 0);
203 
204     {
205         import derelict.util.exception: SharedLibLoadException;
206 
207         assertThrown!SharedLibLoadException(
208             new immutable ConnectionFactory(`wrong/path/to/libpq.dll`)
209         );
210     }
211 
212     assert(!ConnectionFactory.instanced);
213     assert(ReferenceCounter.instances == 0);
214 }
215 
216 // Used only by integration tests facility:
217 private shared ConnectionFactory _connFactory;
218 
219 //TODO: Remove cast and immutable, https://issues.dlang.org/show_bug.cgi?id=22627
220 package immutable(ConnectionFactory) connFactory() { return cast(immutable) _connFactory; }
221 
222 void _initTestsConnectionFactory()
223 {
224     //TODO: ditto
225     _connFactory = cast(shared) new immutable ConnectionFactory;
226 
227     assert(ConnectionFactory.instanced);
228     assert(ReferenceCounter.instances == 1);
229 }