source: gs2-extensions/parallel-building/trunk/src/perllib/dbutil/gdbmserver.pm@ 24674

Last change on this file since 24674 was 24674, checked in by jmt12, 13 years ago

A new database driver that connects to one or more GDBMServers (starting them up as necessary) via GDBMClient objects. This allows for a single persistent GDBM connection (per server).

File size: 10.1 KB
Line 
1###########################################################################
2#
3# dbutil::gdbmserver -- utility functions for writing to gdbm databases but
4# implemented as a server with a single, persistent
5# connection
6#
7# A component of the Greenstone digital library software
8# from the New Zealand Digital Library Project at the
9# University of Waikato, New Zealand.
10#
11# Copyright (C) 2009
12#
13# This program is free software; you can redistribute it and/or modify
14# it under the terms of the GNU General Public License as published by
15# the Free Software Foundation; either version 2 of the License, or
16# (at your option) any later version.
17#
18# This program is distributed in the hope that it will be useful,
19# but WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21# GNU General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, write to the Free Software
25# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26#
27###########################################################################
28
29package dbutil::gdbmserver;
30
31use strict;
32use warnings;
33
34# We're going to have to delve into locking (a little) to prevent multiple
35# threads trying to launch the server at once
36use Fcntl qw(:flock);
37
38use GDBMClient;
39use util;
40
41my $hyphen70 = '-' x 70;
42my $debug = 0;
43
44my %server_lockfile_paths;
45sub _spawnClient
46{
47 my ($infodb_file_path) = @_;
48 # 1. Check whether the server is already running by trying to locate the
49 # server 'lock' file.
50 my ($infodb_file, $extension) = $infodb_file_path =~ /([^\\\/]+)\.(db|gdb)/i;
51 my $server_lockfile_path = &util::filename_cat($ENV{'GSDLHOME'},'tmp','gdbmserver-' . $infodb_file . '.lock');
52 #rint " * Searching for lockfile: " . $server_lockfile_path . "\n";
53 # We need to lock here to ensure only one thread enters the following code,
54 # sees a missing GDBMServer, and launches it
55 my $tmp_lockfile_path = &util::filename_cat($ENV{'GSDLHOME'},'tmp','dbutil-gdbmserver.lock');
56 open(TMPFH, '>', $tmp_lockfile_path) or die ("Error! Failed to open file for writing: " . $tmp_lockfile_path . "\nReason: " . $! . "\n");
57 flock(TMPFH, LOCK_EX) or die("Error! Cannot lock file exclusively: " . $tmp_lockfile_path . "\nReason: " . $! . "\n");
58 print TMPFH localtime();
59 # - If the file doesn't exist...
60 if (!-e $server_lockfile_path)
61 {
62 # ...start it!
63 my $launch_cmd = 'GDBMServer.pl "' . $$ . '" "' . $infodb_file_path . '"';
64 print "* Starting GDBMServer for: " . $infodb_file_path . "\n";
65 # @note I once had the below pipe ending with 2>&1 |, but that then blocks
66 # indefinitely when looping and reading <SERVERIN>.
67 open(SERVERIN, $launch_cmd . ' |') or die("Error! Failed to run launch command: " . $launch_cmd . "\nReason: " . $! . "\n");
68 # read all the output from the server
69 my $line = '';
70 my $server_lock_file_created = 0;
71 while ($line = <SERVERIN>)
72 {
73 # - watch for the line indicating a lock file has been created and
74 # populated with a sexy port number
75 if ($line =~ /Server now listening/)
76 {
77 $server_lock_file_created = 1;
78 }
79 # - we could also watch for errors here
80 if ($debug)
81 {
82 if ($line !~ /\n/)
83 {
84 $line .= "\n";
85 }
86 $|++; # autoflush
87 print "[gdbmserver] $line";
88 $|--; # disable autoflush
89 }
90 }
91 close(SERVERIN);
92 if (!$server_lock_file_created)
93 {
94 die("Error! GDBMServer failed to create lock file. Check server logs.");
95 }
96 # record this for later
97 $server_lockfile_paths{$server_lockfile_path} = $infodb_file_path;
98 }
99 flock(TMPFH, LOCK_UN);
100 close($tmp_lockfile_path);
101 unlink($tmp_lockfile_path);
102 return GDBMClient->new($server_lockfile_path);
103}
104
105END
106{
107 # we ask the server to shutdown, but only the 'creator' thread will actually
108 # be able to, and only once all listeners have deregistered.
109 foreach my $server_lockfile_path (keys (%server_lockfile_paths))
110 {
111 my $infodb_file_path = $server_lockfile_paths{$server_lockfile_path};
112 my $gdbm_client_handle = GDBMClient->new($server_lockfile_path);
113 print "* Stopping GDBMServer for: " . $infodb_file_path . "\n";
114 $gdbm_client_handle->query('!stop:' . $$);
115 }
116 # we should now wait until all of the server_lockfiles have actually been
117 # removed (otherwise people could mistakenly run import/build again
118 # immediately and things *might* go pearshaped).
119 foreach my $server_lockfile_path (keys (%server_lockfile_paths))
120 {
121 # While the file exists, we should wait
122 print "* Waiting for GDBMServer [" . $server_lockfile_path . "] to exit...";
123 if (-e $server_lockfile_path)
124 {
125 while (-e $server_lockfile_path)
126 {
127 print ".";
128 sleep(1);
129 }
130 }
131 print " Done!\n";
132 }
133}
134
135# -----------------------------------------------------------------------------
136# GDBM SERVER IMPLEMENTATION
137# -----------------------------------------------------------------------------
138sub open_infodb_write_handle
139{
140 my $infodb_file_path = shift(@_);
141 my $opt_append = shift(@_);
142 if (defined $opt_append && $opt_append ne "append")
143 {
144 print "Warning! GDBM modes other than 'append' not supported for GDBMServer.\n";
145 }
146 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
147 # Register this client on the server if necessary
148 $gdbm_client_handle->addListener('w');
149 # and pass the handle to client around
150 return $gdbm_client_handle;
151}
152
153# /** Destructor or near enough.
154# /*
155sub close_infodb_write_handle
156{
157 my $gdbm_client_handle = shift(@_);
158 # we deregister as a listener
159 $gdbm_client_handle->removeListener('w');
160}
161# /** close_infodb_write_handle($infodb_handle) **/
162
163# /** @function get_info_db_file_path
164# * Exactly the same as vanilla GDBM - as we are still using a GDBM database
165# * just accessing it via a persistant server
166# */
167sub get_infodb_file_path
168{
169 my $collection_name = shift(@_);
170 my $infodb_directory_path = shift(@_);
171
172 my $infodb_file_extension = ".gdb";
173 my $infodb_file_name = &util::get_dirsep_tail($collection_name) . $infodb_file_extension;
174 return &util::filename_cat($infodb_directory_path, $infodb_file_name);
175}
176
177sub read_infodb_file
178{
179 my $infodb_file_path = shift(@_);
180 my $infodb_map = shift(@_);
181 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
182 $gdbm_client_handle->addListener('r');
183 # retrieves all the keys in the form:
184 # [key1]\n[key2]\n[key3]\n...[keyn]
185 my $raw_infodb_keys = $gdbm_client_handle->query('[*]?');
186
187 my @infodb_keys = split(/\r?\n/, $raw_infodb_keys);
188 foreach my $infodb_key (@infodb_keys)
189 {
190 if ($infodb_key =~ /.+/ && $infodb_key !~ /-{70}/)
191 {
192 # lookup each key
193 my $infodb_value = $gdbm_client_handle->query('[' . $infodb_key . ']?');
194 # store it
195 $infodb_map->{$infodb_key} = $infodb_value;
196 }
197 }
198 # remove our listener
199 $gdbm_client_handle->removeListener('r');
200}
201
202sub read_infodb_keys
203{
204 my $infodb_file_path = shift(@_);
205 my $infodb_map = shift(@_);
206 # spawn client (creating server as necessary)
207 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
208 # register ourself as listener
209 $gdbm_client_handle->addListener('k');
210 # retrieves all the keys in the form:
211 # [key1]\n[key2]\n[key3]\n...[keyn]
212 my $raw_infodb_keys = $gdbm_client_handle->query('[*]?');
213 my @infodb_keys = split(/\r?\n/, $raw_infodb_keys);
214 foreach my $infodb_key (@infodb_keys)
215 {
216 if ($infodb_key =~ /.+/ && $infodb_key !~ /-{70}/)
217 {
218 $infodb_map->{$infodb_key} = 1;
219 }
220 }
221 # remove our listener
222 $gdbm_client_handle->removeListener('k');
223}
224
225sub write_infodb_entry
226{
227 my $gdbm_client_handle = shift(@_);
228 my $infodb_key = shift(@_);
229 my $infodb_map = shift(@_);
230 # - build up the gdbm command
231 my $gdbm_command = "[" . $infodb_key . "]+\n";
232 foreach my $infodb_value_key (keys(%$infodb_map))
233 {
234 foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
235 {
236 if ($infodb_value =~ /-{70,}/)
237 {
238 # if value contains 70 or more hyphens in a row we need to escape them
239 # to prevent txt2db from treating them as a separator
240 $infodb_value =~ s/-/&\#045;/gi;
241 }
242 $gdbm_command .= "<" . $infodb_value_key . ">" . $infodb_value . "\n";
243 }
244 }
245 $gdbm_command .= $hyphen70 . "\n";
246 # - ask the client to transmit the command to the server
247 $gdbm_client_handle->query($gdbm_command);
248}
249
250sub write_infodb_rawentry
251{
252 my $gdbm_client_handle = shift(@_);
253 my $infodb_key = shift(@_);
254 my $infodb_val = shift(@_);
255 # - build up the gdbm command
256 my $gdbm_command = "[" . $infodb_key . "]\n";
257 $gdbm_command .= $infodb_val . "\n";
258 $gdbm_command .= $hyphen70 . "\n";
259 # - ask the client to transmit the command to the server
260 $gdbm_client_handle->query($gdbm_command);
261 return 1;
262}
263
264sub set_infodb_entry
265{
266 my $infodb_file_path = shift(@_);
267 my $infodb_key = shift(@_);
268 my $infodb_map = shift(@_);
269 # spawn client (creating server as necessary)
270 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
271 $gdbm_client_handle->addListener('s');
272 # Protect metadata values that go inside quotes for gdbmset
273 foreach my $k (keys %$infodb_map)
274 {
275 my @escaped_v = ();
276 foreach my $v (@{$infodb_map->{$k}})
277 {
278 if ($k eq "contains")
279 {
280 # protect quotes in ".2;".3 etc
281 $v =~ s/\"/\\\"/g;
282 push(@escaped_v, $v);
283 }
284 else
285 {
286 my $ev = &ghtml::unescape_html($v);
287 $ev =~ s/\"/\\\"/g;
288 push(@escaped_v, $ev);
289 }
290 }
291 $infodb_map->{$k} = \@escaped_v;
292 }
293 # Generate the record string (GDBM command)
294 my $gdbm_command = "[" . $infodb_key . "]\n";
295 $gdbm_command .= &dbutil::convert_infodb_hash_to_string($infodb_map) . "\n";
296 $gdbm_command .= $hyphen70 . "\n";
297 # Send command to server
298 $gdbm_client_handle->query($gdbm_command);
299 # remove our listener
300 $gdbm_client_handle->removeListener('s');
301}
302
303sub delete_infodb_entry
304{
305 my $gdbm_client_handle = shift(@_);
306 my $infodb_key = shift(@_);
307 # - create command
308 my $gdbm_command = "[" . $infodb_key . "]-\n";
309 # - and send
310 $gdbm_client_handle->query($gdbm_command);
311}
312
313
314
3151;
Note: See TracBrowser for help on using the repository browser.