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