source: gs2-extensions/tdb-edit/trunk/src/perllib/dbutil/tdbserver.pm@ 26085

Last change on this file since 26085 was 26085, checked in by jmt12, 12 years ago

Trying to file locking doesn't really work over NFS, and so the code in here to check if a TDBServer is already running (which attempts to get a flock over the tdbserver.lock file) often dies. I've already restructured things so I know a server is running (the Greenstone instance that calls the OpenMPI code starts one) so I don't really need extra testing here - I'll just add a simple file exists test

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