]> git.proxmox.com Git - pve-manager-legacy.git/blob - PVE/APIDaemon.pm
bump version to 3.0-31
[pve-manager-legacy.git] / PVE / APIDaemon.pm
1 package PVE::APIDaemon;
2
3 use strict;
4 use warnings;
5 use POSIX ":sys_wait_h";
6 use Socket qw(IPPROTO_TCP TCP_NODELAY SOMAXCONN);
7 use IO::Socket::INET;
8
9 use PVE::SafeSyslog;
10 use PVE::HTTPServer;
11
12 my $workers = {};
13
14 sub new {
15 my ($this, %args) = @_;
16
17 my $class = ref($this) || $this;
18
19 die "no lockfile" if !$args{lockfile};
20
21 my $lockfh = IO::File->new(">>$args{lockfile}") ||
22 die "unable to open lock file '$args{lockfile}' - $!\n";
23
24 my $socket = IO::Socket::INET->new(
25 LocalAddr => $args{host} || undef,
26 LocalPort => $args{port} || 80,
27 Listen => SOMAXCONN,
28 Proto => 'tcp',
29 ReuseAddr => 1) ||
30 die "unable to create socket - $@\n";
31
32 # we ofter observe delays when using Nagle algorithm,
33 # so we disable that to maximize performance
34 setsockopt($socket, IPPROTO_TCP, TCP_NODELAY, 1);
35
36 my $cfg = { %args };
37 my $self = bless { cfg => $cfg }, $class;
38
39 $cfg->{socket} = $socket;
40 $cfg->{lockfh} = $lockfh;
41 $cfg->{max_workers} = 3 if !$cfg->{max_workers};
42 $cfg->{trusted_env} = 0 if !defined($cfg->{trusted_env});
43
44 return $self;
45 }
46
47 sub worker_finished {
48 my $cpid = shift;
49
50 syslog('info', "worker $cpid finished");
51 }
52
53 sub finish_workers {
54 local $!; local $?;
55 foreach my $cpid (keys %$workers) {
56 my $waitpid = waitpid ($cpid, WNOHANG);
57 if (defined($waitpid) && ($waitpid == $cpid)) {
58 delete ($workers->{$cpid});
59 worker_finished ($cpid);
60 }
61 }
62 }
63
64 sub test_workers {
65 foreach my $cpid (keys %$workers) {
66 if (!kill(0, $cpid)) {
67 waitpid($cpid, POSIX::WNOHANG());
68 delete $workers->{$cpid};
69 worker_finished ($cpid);
70 }
71 }
72 }
73
74 sub start_workers {
75 my ($self) = @_;
76
77 my $count = 0;
78 foreach my $cpid (keys %$workers) {
79 $count++;
80 }
81
82 my $need = $self->{cfg}->{max_workers} - $count;
83
84 return if $need <= 0;
85
86 syslog('info', "starting $need worker(s)");
87
88 while ($need > 0) {
89 my $pid = fork;
90
91 if (!defined ($pid)) {
92 syslog('err', "can't fork worker");
93 sleep (1);
94 } elsif ($pid) { #parent
95 $workers->{$pid} = 1;
96 syslog('info', "worker $pid started");
97 $need--;
98 } else {
99 $0 = "$0 worker";
100
101 $SIG{TERM} = $SIG{QUIT} = 'DEFAULT'; # we handle that with AnyEvent
102
103 eval {
104 my $server = PVE::HTTPServer->new(%{$self->{cfg}});
105 $server->run();
106 };
107 if (my $err = $@) {
108 syslog('err', $err);
109 sleep(5); # avoid fast restarts
110 }
111 exit (0);
112 }
113 }
114 }
115
116 sub terminate_server {
117
118 syslog('info', "received terminate request");
119
120 foreach my $cpid (keys %$workers) {
121 kill (15, $cpid); # TERM childs
122 }
123
124 # nicely shutdown childs (give them max 10 seconds to shut down)
125 my $previous_alarm = alarm (10);
126 eval {
127 local $SIG{ALRM} = sub { die "timeout\n" };
128
129 while ((my $pid = waitpid (-1, 0)) > 0) {
130 if (defined($workers->{$pid})) {
131 delete ($workers->{$pid});
132 worker_finished ($pid);
133 }
134 }
135 alarm(0); # avoid race condition
136 };
137 my $err = $@;
138
139 alarm ($previous_alarm);
140
141 if ($err) {
142 syslog('err', "error stopping workers (will kill them now) - $err");
143 foreach my $cpid (keys %$workers) {
144 # KILL childs still alive!
145 if (kill (0, $cpid)) {
146 delete ($workers->{$cpid});
147 syslog("err", "kill worker $cpid");
148 kill (9, $cpid);
149 }
150 }
151 }
152 }
153
154 sub start_server {
155 my $self = shift;
156
157 eval {
158 my $old_sig_chld = $SIG{CHLD};
159 local $SIG{CHLD} = sub {
160 finish_workers ();
161 &$old_sig_chld(@_) if $old_sig_chld;
162 };
163
164 my $old_sig_term = $SIG{TERM};
165 local $SIG{TERM} = sub {
166 terminate_server ();
167 &$old_sig_term(@_) if $old_sig_term;
168 };
169 local $SIG{QUIT} = sub {
170 terminate_server();
171 &$old_sig_term(@_) if $old_sig_term;
172 };
173
174 local $SIG{HUP} = sub {
175 syslog("info", "received reload request");
176 foreach my $cpid (keys %$workers) {
177 kill (15, $cpid); # kill childs
178 }
179 };
180
181 for (;;) { # forever
182 $self->start_workers();
183 sleep (5);
184 $self->test_workers();
185 }
186 };
187 my $err = $@;
188
189 if ($err) {
190 syslog('err', "ERROR: $err");
191 }
192 }
193
194 1;