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

Last change on this file since 26994 was 26994, checked in by jmt12, 11 years ago

Adding some more debug messages to ensure TDBServer is starting on the correct host. Eventually found I had localhost hardcoded in the TDBServer.pl launcher and that isn't the write interface for compute nodes to see it (needs to be on medusa.local)

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