1 package MogileFS
::Util
;
5 use MogileFS
::Exception
;
6 use MogileFS
::DeviceState
;
9 our @ISA = qw(Exporter);
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 apply_state_events_list
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
20 sub apply_state_events
{
21 my @events = split(/\s/, ${$_[0]});
22 shift @events; # pop the :monitor_events part
23 apply_state_events_list
(@events);
26 sub apply_state_events_list
{
27 # This will needlessly fetch domain/class/host most of the time.
28 # Maybe replace with something that "caches" factories?
29 my %factories = ( 'domain' => MogileFS
::Factory
::Domain
->get_factory,
30 'class' => MogileFS
::Factory
::Class
->get_factory,
31 'host' => MogileFS
::Factory
::Host
->get_factory,
32 'device' => MogileFS
::Factory
::Device
->get_factory, );
35 my $args = decode_url_args
($ev);
36 my $mode = delete $args->{ev_mode
};
37 my $type = delete $args->{ev_type
};
38 my $id = delete $args->{ev_id
};
40 # This special case feels gross, but that's what it is.
41 if ($type eq 'srvset') {
42 my $val = $mode eq 'set' ?
$args->{value
} : undef;
43 MogileFS
::Config
->cache_server_setting($id, $val);
47 my $old = $factories{$type}->get_by_id($id);
48 if ($mode eq 'setstate') {
50 # FIXME: Make objects slightly mutable and directly set fields?
51 $factories{$type}->set({ %{$old->fields}, %$args });
52 } elsif ($mode eq 'set') {
53 # Re-add any observed data.
54 my $observed = $old ?
$old->observed_fields : {};
55 $factories{$type}->set({ %$args, %$observed });
56 } elsif ($mode eq 'remove') {
57 $factories{$type}->remove($old) if $old;
63 my ($delay, $code) = @_;
64 my ($worker, $psock_fd);
65 if ($worker = MogileFS
::ProcManager
->is_child) {
66 $psock_fd = $worker->psock_fd;
70 my $start = Time
::HiRes
::time();
71 my $explicit_sleep = undef;
73 # run the code in a loop, so "next" will get out of it.
76 $explicit_sleep = shift;
80 my $now = Time
::HiRes
::time();
81 my $took = $now - $start;
82 my $sleep_for = defined $explicit_sleep ?
$explicit_sleep : ($delay - $took);
84 # simple case, not in a child process (this never happens currently)
86 Time
::HiRes
::sleep($sleep_for);
90 Time
::HiRes
::sleep($sleep_for) if $sleep_for > 0;
91 #local $Mgd::POST_SLEEP_DEBUG = 1;
92 # This calls read_from_parent. Workers used to needlessly call
93 # parent_ping constantly.
99 my ($msg, $level) = @_;
100 return unless $Mgd::DEBUG
>= 1;
101 $msg =~ s/[\r\n]+//g;
102 if (my $worker = MogileFS
::ProcManager
->is_child) {
103 $worker->send_to_parent("debug $msg");
105 my $dbg = "[debug] $msg";
106 MogileFS
::ProcManager
->NoteError(\
$dbg);
107 Mgd
::log('debug', $msg);
114 $last_error = $errmsg;
115 if (my $worker = MogileFS
::ProcManager
->is_child) {
116 my $msg = "error $errmsg";
118 $worker->send_to_parent($msg);
120 MogileFS
::ProcManager
->NoteError(\
$errmsg);
121 Mgd
::log('debug', $errmsg);
126 # like error(), but returns undef.
144 MogileFS
::Exception
->new($errcode)->throw;
149 return "" unless UNIVERSAL
::isa
($ex, "MogileFS::Exception");
154 my($pid, $sess_id, $i);
156 ## Fork and exit parent
157 if ($pid = fork) { exit 0; }
159 ## Detach ourselves from the terminal
160 croak
"Cannot detach from controlling terminal"
161 unless $sess_id = POSIX
::setsid
();
163 ## Prevent possibility of acquiring a controlling terminal
164 $SIG{'HUP'} = 'IGNORE';
165 if ($pid = fork) { exit 0; }
167 ## Change working directory
170 ## Clear file creation mask
173 print STDERR
"Daemon running as pid $$.\n" if $MogileFS::DEBUG
;
175 ## Close open file descriptors
180 ## Reopen STDERR, STDOUT, STDIN to /dev/null
181 if ( $MogileFS::DEBUG
) {
182 open(STDIN
, "+>/tmp/mogilefsd.log");
184 open(STDIN
, "+>/dev/null");
186 open(STDOUT
, "+>&STDIN");
187 open(STDERR
, "+>&STDIN");
191 # given an array of arrayrefs of [ item, weight ], returns weighted randomized
192 # list of items (without the weights, not arrayref; just list)
194 # a weight of 0 means to exclude that item from the results list; i.e. it's not
198 # my @items = weighted_list( [ 1, 10 ], [ 2, 20 ], [ 3, 0 ] );
200 # returns (1, 2) or (2, 1) with the latter far more likely
201 sub weighted_list
(@
) {
202 my @list = grep { $_->[1] > 0 } @_;
206 $sum += $_->[1] foreach @list;
209 return shift(@list)->[0]
210 if scalar(@list) == 1;
212 my $val = rand() * $sum;
214 for (my $idx = 0; $idx < scalar(@list); $idx++) {
215 my $item = $list[$idx];
216 $curval += $item->[1];
217 if ($curval >= $val) {
218 my ($ret) = splice(@list, $idx, 1);
225 push @ret, $getone->() while @list;
229 # given a file descriptor number and a timeout, wait for that descriptor to
230 # become readable; returns 0 or 1 on if it did or not
231 sub wait_for_readability
{
232 my ($fileno, $timeout) = @_;
233 return 0 unless $fileno && $timeout >= 0;
236 vec($rin, $fileno, 1) = 1;
237 my $nfound = select($rin, undef, undef, $timeout);
239 # nfound can be undef or 0, both failures, or 1, a success
240 return $nfound ?
1 : 0;
243 sub wait_for_writeability
{
244 my ($fileno, $timeout) = @_;
245 return 0 unless $fileno && $timeout;
248 vec($rout, $fileno, 1) = 1;
249 my $nfound = select(undef, $rout, undef, $timeout);
251 # nfound can be undef or 0, both failures, or 1, a success
252 return $nfound ?
1 : 0;
257 return $n1 if $n1 > $n2;
263 return $n1 if $n1 < $n2;
270 return $_ if $code->();
276 my ($href, @okay) = @_;
278 delete $left{$_} foreach @okay;
279 return 1 unless %left;
280 Carp
::croak
("Unknown argument(s): " . join(", ", sort keys %left));
285 return MogileFS
::DeviceState
->of_string($state);
289 my $a = defined $_[0] ?
$_[0] : "";
290 $a =~ s/([^a-zA-Z0-9_\,\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg
;
295 sub encode_url_args
{
297 return join('&', map { eurl
($_) . "=" . eurl
($args->{$_}) } keys %$args);
300 sub decode_url_args
{
302 my $buffer = ref $a ?
$a : \
$a;
306 my @pairs = grep { $_ } split(/&/, $$buffer);
308 foreach $pair (@pairs)
310 ($name, $value) = split(/=/, $pair);
312 $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
314 $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
315 $ret->{$name} .= $ret->{$name} ?
"\0$value" : $value;