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

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

Made it so listeners are only removed when the object is deallocated (to ensure it sticks around for the entire import and build process assuming you have some top level script start the server/add the first listener). Extended get_infodb_file_path to do exactly that.

File size: 11.2 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
44# We have a global reference to all of the GDBM Server lockfiles that this
45# instance has created (as we'll be responsible for closing them)
46my %created_server_lockfile_paths;
47# Keep track of the lockfiles for server we have added ourselves as listeners
48# to.
49my %listener_server_lockfile_paths;
50# We also have a global of all of the listeners we have assigned as we'll
51# be responsible for removing them.
52my %registered_listeners;
53
54sub _spawnClient
55{
56 my ($infodb_file_path) = @_;
57 # 1. Check whether the server is already running by trying to locate the
58 # server 'lock' file.
59 my ($infodb_file, $extension) = $infodb_file_path =~ /([^\\\/]+)\.(db|gdb)/i;
60 my $server_lockfile_path = &util::filename_cat($ENV{'GSDLHOME'},'tmp','gdbmserver-' . $infodb_file . '.lock');
61 #rint " * Searching for lockfile: " . $server_lockfile_path . "\n";
62 # We need to lock here to ensure only one thread enters the following code,
63 # sees a missing GDBMServer, and launches it
64 my $tmp_lockfile_path = &util::filename_cat($ENV{'GSDLHOME'},'tmp','dbutil-gdbmserver.lock');
65 open(TMPFH, '>', $tmp_lockfile_path) or die ("Error! Failed to open file for writing: " . $tmp_lockfile_path . "\nReason: " . $! . "\n");
66 flock(TMPFH, LOCK_EX) or die("Error! Cannot lock file exclusively: " . $tmp_lockfile_path . "\nReason: " . $! . "\n");
67 print TMPFH localtime();
68 # - If the file doesn't exist...
69 if (!-e $server_lockfile_path)
70 {
71 # ...start it!
72 my $launch_cmd = 'GDBMServer.pl "' . $$ . '" "' . $infodb_file_path . '"';
73 print "* Starting GDBMServer for: " . $infodb_file_path . "\n";
74 # @note I once had the below pipe ending with 2>&1 |, but that then blocks
75 # indefinitely when looping and reading <SERVERIN>.
76 open(SERVERIN, $launch_cmd . ' |') or die("Error! Failed to run launch command: " . $launch_cmd . "\nReason: " . $! . "\n");
77 # read all the output from the server
78 my $line = '';
79 my $server_lock_file_created = 0;
80 while ($line = <SERVERIN>)
81 {
82 # - watch for the line indicating a lock file has been created and
83 # populated with a sexy port number
84 if ($line =~ /Server now listening/)
85 {
86 $server_lock_file_created = 1;
87 }
88 # - we could also watch for errors here
89 if ($debug)
90 {
91 if ($line !~ /\n/)
92 {
93 $line .= "\n";
94 }
95 $|++; # autoflush
96 print "[gdbmserver] $line";
97 $|--; # disable autoflush
98 }
99 }
100 close(SERVERIN);
101 if (!$server_lock_file_created)
102 {
103 die("Error! GDBMServer failed to create lock file. Check server logs.");
104 }
105 # record this for later
106 $created_server_lockfile_paths{$server_lockfile_path} = 1;
107 }
108 flock(TMPFH, LOCK_UN);
109 close($tmp_lockfile_path);
110 unlink($tmp_lockfile_path);
111 # record this for later
112 $listener_server_lockfile_paths{$server_lockfile_path} = $infodb_file_path;
113 return GDBMClient->new($server_lockfile_path);
114}
115
116END
117{
118 # we ask the server to shutdown, but only the 'creator' thread will actually
119 # be able to, and only once all listeners have deregistered.
120 foreach my $server_lockfile_path (keys (%listener_server_lockfile_paths))
121 {
122 my $infodb_file_path = $listener_server_lockfile_paths{$server_lockfile_path};
123 my $gdbm_client_handle = GDBMClient->new($server_lockfile_path);
124 # Deregister all of our registered listeners
125 foreach my $listener_suffix (keys(%registered_listeners))
126 {
127 $gdbm_client_handle->removeListener($listener_suffix);
128 }
129 # ask the servers we created to shut down (all other threads will have
130 # this request ignored)
131 if (defined $created_server_lockfile_paths{$infodb_file_path})
132 {
133 print "* Attempting to stop GDBMServer for: " . $infodb_file_path . "\n";
134 }
135 $gdbm_client_handle->query('!stop:' . $$);
136 }
137 # we should now wait until all of our server_lockfiles have actually been
138 # removed (otherwise people could mistakenly run import/build again
139 # immediately and things *might* go pearshaped).
140 foreach my $server_lockfile_path (keys (%created_server_lockfile_paths))
141 {
142 # While the file exists, we should wait
143 print "* Waiting for GDBMServer [" . $server_lockfile_path . "] to exit...";
144 if (-e $server_lockfile_path)
145 {
146 while (-e $server_lockfile_path)
147 {
148 print ".";
149 sleep(1);
150 }
151 }
152 print " Done!\n";
153 }
154}
155
156# -----------------------------------------------------------------------------
157# GDBM SERVER IMPLEMENTATION
158# -----------------------------------------------------------------------------
159sub open_infodb_write_handle
160{
161 my $infodb_file_path = shift(@_);
162 my $opt_append = shift(@_);
163 if (defined $opt_append && $opt_append ne "append")
164 {
165 print "Warning! GDBM modes other than 'append' not supported for GDBMServer.\n";
166 }
167 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
168 # Register this client on the server if necessary
169 $gdbm_client_handle->addListener('w');
170 $registered_listeners{'w'} = 1;
171 # and pass the handle to client around
172 return $gdbm_client_handle;
173}
174
175# /** Destructor or near enough.
176# /*
177sub close_infodb_write_handle
178{
179 my $gdbm_client_handle = shift(@_);
180}
181# /** close_infodb_write_handle($infodb_handle) **/
182
183# /** @function get_info_db_file_path
184# * Exactly the same as vanilla GDBM - as we are still using a GDBM database
185# * just accessing it via a persistant server
186# */
187sub get_infodb_file_path
188{
189 my $collection_name = shift(@_);
190 my $infodb_directory_path = shift(@_);
191 my $create_server = shift(@_);
192
193 my $infodb_file_extension = ".gdb";
194 my $infodb_file_name = &util::get_dirsep_tail($collection_name) . $infodb_file_extension;
195 my $infodb_file_path = &util::filename_cat($infodb_directory_path, $infodb_file_name);
196
197 # Special Case for GDBMServer
198 if (defined $create_server && $create_server == 1)
199 {
200 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
201 # Register this client on the server if necessary
202 $gdbm_client_handle->addListener('i');
203 $registered_listeners{'i'} = 1;
204 }
205
206 # Resuming our regular programming
207 return $infodb_file_path;
208}
209
210sub read_infodb_file
211{
212 my $infodb_file_path = shift(@_);
213 my $infodb_map = shift(@_);
214 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
215 $gdbm_client_handle->addListener('r');
216 $registered_listeners{'r'} = 1;
217 # retrieves all the keys in the form:
218 # [key1]\n[key2]\n[key3]\n...[keyn]
219 my $raw_infodb_keys = $gdbm_client_handle->query('[*]?');
220
221 my @infodb_keys = split(/\r?\n/, $raw_infodb_keys);
222 foreach my $infodb_key (@infodb_keys)
223 {
224 if ($infodb_key =~ /.+/ && $infodb_key !~ /-{70}/)
225 {
226 # lookup each key
227 my $infodb_value = $gdbm_client_handle->query('[' . $infodb_key . ']?');
228 # store it
229 $infodb_map->{$infodb_key} = $infodb_value;
230 }
231 }
232}
233
234sub read_infodb_keys
235{
236 my $infodb_file_path = shift(@_);
237 my $infodb_map = shift(@_);
238 # spawn client (creating server as necessary)
239 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
240 # register ourself as listener
241 $gdbm_client_handle->addListener('k');
242 $registered_listeners{'k'} = 1;
243 # retrieves all the keys in the form:
244 # [key1]\n[key2]\n[key3]\n...[keyn]
245 my $raw_infodb_keys = $gdbm_client_handle->query('[*]?');
246 my @infodb_keys = split(/\r?\n/, $raw_infodb_keys);
247 foreach my $infodb_key (@infodb_keys)
248 {
249 if ($infodb_key =~ /.+/ && $infodb_key !~ /-{70}/)
250 {
251 $infodb_map->{$infodb_key} = 1;
252 }
253 }
254}
255
256sub write_infodb_entry
257{
258 my $gdbm_client_handle = shift(@_);
259 my $infodb_key = shift(@_);
260 my $infodb_map = shift(@_);
261 # - build up the gdbm command
262 my $gdbm_command = "[" . $infodb_key . "]+\n";
263 foreach my $infodb_value_key (keys(%$infodb_map))
264 {
265 foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
266 {
267 if ($infodb_value =~ /-{70,}/)
268 {
269 # if value contains 70 or more hyphens in a row we need to escape them
270 # to prevent txt2db from treating them as a separator
271 $infodb_value =~ s/-/&\#045;/gi;
272 }
273 $gdbm_command .= "<" . $infodb_value_key . ">" . $infodb_value . "\n";
274 }
275 }
276 $gdbm_command .= $hyphen70 . "\n";
277 # - ask the client to transmit the command to the server
278 $gdbm_client_handle->query($gdbm_command);
279}
280
281sub write_infodb_rawentry
282{
283 my $gdbm_client_handle = shift(@_);
284 my $infodb_key = shift(@_);
285 my $infodb_val = shift(@_);
286 # - build up the gdbm command
287 my $gdbm_command = "[" . $infodb_key . "]\n";
288 $gdbm_command .= $infodb_val . "\n";
289 $gdbm_command .= $hyphen70 . "\n";
290 # - ask the client to transmit the command to the server
291 $gdbm_client_handle->query($gdbm_command);
292 return 1;
293}
294
295sub set_infodb_entry
296{
297 my $infodb_file_path = shift(@_);
298 my $infodb_key = shift(@_);
299 my $infodb_map = shift(@_);
300 # spawn client (creating server as necessary)
301 my $gdbm_client_handle = &_spawnClient($infodb_file_path);
302 $gdbm_client_handle->addListener('s');
303 $registered_listeners{'s'} = 1;
304 # Protect metadata values that go inside quotes for gdbmset
305 foreach my $k (keys %$infodb_map)
306 {
307 my @escaped_v = ();
308 foreach my $v (@{$infodb_map->{$k}})
309 {
310 if ($k eq "contains")
311 {
312 # protect quotes in ".2;".3 etc
313 $v =~ s/\"/\\\"/g;
314 push(@escaped_v, $v);
315 }
316 else
317 {
318 my $ev = &ghtml::unescape_html($v);
319 $ev =~ s/\"/\\\"/g;
320 push(@escaped_v, $ev);
321 }
322 }
323 $infodb_map->{$k} = \@escaped_v;
324 }
325 # Generate the record string (GDBM command)
326 my $gdbm_command = "[" . $infodb_key . "]\n";
327 $gdbm_command .= &dbutil::convert_infodb_hash_to_string($infodb_map) . "\n";
328 $gdbm_command .= $hyphen70 . "\n";
329 # Send command to server
330 $gdbm_client_handle->query($gdbm_command);
331}
332
333sub delete_infodb_entry
334{
335 my $gdbm_client_handle = shift(@_);
336 my $infodb_key = shift(@_);
337 # - create command
338 my $gdbm_command = "[" . $infodb_key . "]-\n";
339 # - and send
340 $gdbm_client_handle->query($gdbm_command);
341}
342
343
344
3451;
Note: See TracBrowser for help on using the repository browser.