Checking in changes prior to tagging of version 2.66.
[MogileFS-Server.git] / lib / MogileFS / Util.pm
blob10eacfa86c82b82d8848c40961aff4e7615e38a0
1 package MogileFS::Util;
2 use strict;
3 use Carp qw(croak);
4 use Time::HiRes;
5 use MogileFS::Exception;
6 use MogileFS::DeviceState;
8 require Exporter;
9 our @ISA = qw(Exporter);
10 our @EXPORT_OK = qw(
11 error undeferr debug fatal daemonize weighted_list every
12 wait_for_readability wait_for_writeability throw error_code
13 max min first okay_args device_state eurl decode_url_args
14 encode_url_args apply_state_events
17 # Applies monitor-job-supplied state events against the factory singletons.
18 # Sad this couldn't be an object method, but ProcManager doesn't base off
19 # anything common.
20 sub apply_state_events {
21 my @events = split(/\s/, ${$_[0]});
22 shift @events; # pop the :monitor_events part
24 # This will needlessly fetch domain/class/host most of the time.
25 # Maybe replace with something that "caches" factories?
26 my %factories = ( 'domain' => MogileFS::Factory::Domain->get_factory,
27 'class' => MogileFS::Factory::Class->get_factory,
28 'host' => MogileFS::Factory::Host->get_factory,
29 'device' => MogileFS::Factory::Device->get_factory, );
31 for my $ev (@events) {
32 my $args = decode_url_args($ev);
33 my $mode = delete $args->{ev_mode};
34 my $type = delete $args->{ev_type};
35 my $id = delete $args->{ev_id};
37 # This special case feels gross, but that's what it is.
38 if ($type eq 'srvset') {
39 my $val = $mode eq 'set' ? $args->{value} : undef;
40 MogileFS::Config->cache_server_setting($id, $val);
41 next;
44 my $old = $factories{$type}->get_by_id($id);
45 if ($mode eq 'setstate') {
46 # Host/Device only.
47 # FIXME: Make objects slightly mutable and directly set fields?
48 $factories{$type}->set({ %{$old->fields}, %$args });
49 } elsif ($mode eq 'set') {
50 # Re-add any observed data.
51 my $observed = $old ? $old->observed_fields : {};
52 $factories{$type}->set({ %$args, %$observed });
53 } elsif ($mode eq 'remove') {
54 $factories{$type}->remove($old) if $old;
59 sub every {
60 my ($delay, $code) = @_;
61 my ($worker, $psock_fd);
62 if ($worker = MogileFS::ProcManager->is_child) {
63 $psock_fd = $worker->psock_fd;
65 CODERUN:
66 while (1) {
67 my $start = Time::HiRes::time();
68 my $explicit_sleep = undef;
70 # run the code in a loop, so "next" will get out of it.
71 foreach (1) {
72 $code->(sub {
73 $explicit_sleep = shift;
74 });
77 my $now = Time::HiRes::time();
78 my $took = $now - $start;
79 my $sleep_for = defined $explicit_sleep ? $explicit_sleep : ($delay - $took);
81 # simple case, not in a child process (this never happens currently)
82 unless ($psock_fd) {
83 Time::HiRes::sleep($sleep_for);
84 next;
87 Time::HiRes::sleep($sleep_for) if $sleep_for > 0;
88 #local $Mgd::POST_SLEEP_DEBUG = 1;
89 # This calls read_from_parent. Workers used to needlessly call
90 # parent_ping constantly.
91 $worker->parent_ping;
95 sub debug {
96 my ($msg, $level) = @_;
97 return unless $Mgd::DEBUG >= 1;
98 $msg =~ s/[\r\n]+//g;
99 if (my $worker = MogileFS::ProcManager->is_child) {
100 $worker->send_to_parent("debug $msg");
101 } else {
102 my $dbg = "[debug] $msg";
103 MogileFS::ProcManager->NoteError(\$dbg);
104 Mgd::log('debug', $msg);
108 our $last_error;
109 sub error {
110 my ($errmsg) = @_;
111 $last_error = $errmsg;
112 if (my $worker = MogileFS::ProcManager->is_child) {
113 my $msg = "error $errmsg";
114 $msg =~ s/\s+$//;
115 $worker->send_to_parent($msg);
116 } else {
117 MogileFS::ProcManager->NoteError(\$errmsg);
118 Mgd::log('debug', $errmsg);
120 return 0;
123 # like error(), but returns undef.
124 sub undeferr {
125 error(@_);
126 return undef;
129 sub last_error {
130 return $last_error;
133 sub fatal {
134 my ($errmsg) = @_;
135 error($errmsg);
136 die $errmsg;
139 sub throw {
140 my ($errcode) = @_;
141 MogileFS::Exception->new($errcode)->throw;
144 sub error_code {
145 my ($ex) = @_;
146 return "" unless UNIVERSAL::isa($ex, "MogileFS::Exception");
147 return $ex->code;
150 sub daemonize {
151 my($pid, $sess_id, $i);
153 ## Fork and exit parent
154 if ($pid = fork) { exit 0; }
156 ## Detach ourselves from the terminal
157 croak "Cannot detach from controlling terminal"
158 unless $sess_id = POSIX::setsid();
160 ## Prevent possibility of acquiring a controlling terminal
161 $SIG{'HUP'} = 'IGNORE';
162 if ($pid = fork) { exit 0; }
164 ## Change working directory
165 chdir "/";
167 ## Clear file creation mask
168 umask 0;
170 print STDERR "Daemon running as pid $$.\n" if $MogileFS::DEBUG;
172 ## Close open file descriptors
173 close(STDIN);
174 close(STDOUT);
175 close(STDERR);
177 ## Reopen STDERR, STDOUT, STDIN to /dev/null
178 if ( $MogileFS::DEBUG ) {
179 open(STDIN, "+>/tmp/mogilefsd.log");
180 } else {
181 open(STDIN, "+>/dev/null");
183 open(STDOUT, "+>&STDIN");
184 open(STDERR, "+>&STDIN");
187 # input:
188 # given an array of arrayrefs of [ item, weight ], returns weighted randomized
189 # list of items (without the weights, not arrayref; just list)
191 # a weight of 0 means to exclude that item from the results list; i.e. it's not
192 # ever used
194 # example:
195 # my @items = weighted_list( [ 1, 10 ], [ 2, 20 ], [ 3, 0 ] );
197 # returns (1, 2) or (2, 1) with the latter far more likely
198 sub weighted_list (@) {
199 my @list = grep { $_->[1] > 0 } @_;
200 my @ret;
202 my $sum = 0;
203 $sum += $_->[1] foreach @list;
205 my $getone = sub {
206 return shift(@list)->[0]
207 if scalar(@list) == 1;
209 my $val = rand() * $sum;
210 my $curval = 0;
211 for (my $idx = 0; $idx < scalar(@list); $idx++) {
212 my $item = $list[$idx];
213 $curval += $item->[1];
214 if ($curval >= $val) {
215 my ($ret) = splice(@list, $idx, 1);
216 $sum -= $item->[1];
217 return $ret->[0];
222 push @ret, $getone->() while @list;
223 return @ret;
226 # given a file descriptor number and a timeout, wait for that descriptor to
227 # become readable; returns 0 or 1 on if it did or not
228 sub wait_for_readability {
229 my ($fileno, $timeout) = @_;
230 return 0 unless $fileno && $timeout >= 0;
232 my $rin = '';
233 vec($rin, $fileno, 1) = 1;
234 my $nfound = select($rin, undef, undef, $timeout);
236 # nfound can be undef or 0, both failures, or 1, a success
237 return $nfound ? 1 : 0;
240 sub wait_for_writeability {
241 my ($fileno, $timeout) = @_;
242 return 0 unless $fileno && $timeout;
244 my $rout = '';
245 vec($rout, $fileno, 1) = 1;
246 my $nfound = select(undef, $rout, undef, $timeout);
248 # nfound can be undef or 0, both failures, or 1, a success
249 return $nfound ? 1 : 0;
252 # if given an HTTP URL, break it down into [ host, port, URI ], else
253 # returns die, because we don't support non-http-mode anymore
254 sub url_parts {
255 my $path = shift;
256 if ($path =~ m!^http://(.+?)(?::(\d+))?(/.+)$!) {
257 return [ $1, $2 || 80, $3 ];
259 Carp::croak("Bogus URL: $path");
262 sub max {
263 my ($n1, $n2) = @_;
264 return $n1 if $n1 > $n2;
265 return $n2;
268 sub min {
269 my ($n1, $n2) = @_;
270 return $n1 if $n1 < $n2;
271 return $n2;
274 sub first (&@) {
275 my $code = shift;
276 foreach (@_) {
277 return $_ if $code->();
279 undef;
282 sub okay_args {
283 my ($href, @okay) = @_;
284 my %left = %$href;
285 delete $left{$_} foreach @okay;
286 return 1 unless %left;
287 Carp::croak("Unknown argument(s): " . join(", ", sort keys %left));
290 sub device_state {
291 my ($state) = @_;
292 return MogileFS::DeviceState->of_string($state);
295 sub eurl {
296 my $a = defined $_[0] ? $_[0] : "";
297 $a =~ s/([^a-zA-Z0-9_\,\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg;
298 $a =~ tr/ /+/;
299 return $a;
302 sub encode_url_args {
303 my $args = shift;
304 return join('&', map { eurl($_) . "=" . eurl($args->{$_}) } keys %$args);
307 sub decode_url_args {
308 my $a = shift;
309 my $buffer = ref $a ? $a : \$a;
310 my $ret = {};
312 my $pair;
313 my @pairs = grep { $_ } split(/&/, $$buffer);
314 my ($name, $value);
315 foreach $pair (@pairs)
317 ($name, $value) = split(/=/, $pair);
318 $value =~ tr/+/ /;
319 $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
320 $name =~ tr/+/ /;
321 $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
322 $ret->{$name} .= $ret->{$name} ? "\0$value" : $value;
324 return $ret;