File Coverage

blib/lib/PLS/Server.pm
Criterion Covered Total %
statement 160 164 97.5
branch 33 48 68.7
condition 4 9 44.4
subroutine 38 40 95.0
pod 0 11 0.0
total 235 272 86.4


line stmt bran cond sub pod time code
1              
2             use strict;
3 9     9   690661 use warnings;
  9         73  
  9         391  
4 9     9   44  
  9         18  
  9         203  
5             use Future;
6 9     9   5043 use Future::Queue;
  9         94936  
  9         238  
7 9     9   3147 use Future::Utils;
  9         2655  
  9         242  
8 9     9   3705 use IO::Async::Loop;
  9         18120  
  9         344  
9 9     9   5799 use IO::Async::Signal;
  9         260042  
  9         281  
10 9     9   3231 use IO::Async::Stream;
  9         35420  
  9         237  
11 9     9   4605 use IO::Async::Timer::Periodic;
  9         186389  
  9         287  
12 9     9   3549 use IO::Handle;
  9         18345  
  9         243  
13 9     9   46 use Scalar::Util qw(blessed);
  9         26  
  9         281  
14 9     9   36  
  9         18  
  9         290  
15             use PLS::JSON;
16 9     9   3061 use PLS::Server::Request::Factory;
  9         18  
  9         414  
17 9     9   3335 use PLS::Server::Response;
  9         123  
  9         1010  
18 9     9   62 use PLS::Server::Response::Cancelled;
  9         10  
  9         188  
19 9     9   3362  
  9         19  
  9         15510  
20             =head1 NAME
21              
22             PLS::Server
23              
24             =head1 DESCRIPTION
25              
26             Perl Language Server
27              
28             This server communicates to a language client through STDIN/STDOUT.
29              
30             =head1 SYNOPSIS
31              
32             my $server = PLS::Server->new();
33             my $exit_code = $server->run();
34              
35             exit $exit_code;
36              
37             =cut
38              
39             {
40             my ($class) = @_;
41              
42 7     7 0 70509469 return
43             bless {
44             loop => IO::Async::Loop->new(),
45 7         847 stream => undef,
46             running_futures => {},
47             pending_requests => {}
48             }, $class;
49             } ## end sub new
50              
51             {
52             my ($self) = @_;
53              
54             $self->{client_requests} = Future::Queue->new();
55 7     7 0 1277 $self->{client_responses} = Future::Queue->new();
56             $self->{server_requests} = Future::Queue->new();
57 7         952 $self->{server_responses} = Future::Queue->new();
58 7         696  
59 7         544 Future::Utils::repeat
60 7         438 {
61             $self->{client_requests}->shift->on_done(
62             sub {
63             my ($request) = @_;
64              
65             $self->handle_client_request($request);
66 17         3881 return;
67             }
68 17         131 );
69 17         197 } ## end Future::Utils::repeat
70             while => sub { 1 };
71 24     24   1992  
72             Future::Utils::repeat
73 7     17   977 {
  17         1115  
74             $self->{client_responses}->shift->on_done(
75             sub {
76             my ($response) = @_;
77              
78             $self->handle_client_response($response);
79 4         881 return;
80             }
81 4         69 );
82 4         190 } ## end Future::Utils::repeat
83             while => sub { 1 };
84 11     11   726  
85             Future::Utils::repeat
86 7     4   5753 {
  4         621  
87             $self->{server_requests}->shift->on_done(
88             sub {
89             my ($request) = @_;
90             $self->handle_server_request($request);
91             return;
92 29         5093 }
93 29         134 );
94 29         63 } ## end Future::Utils::repeat
95             while => sub { 1 };
96 36     36   1108  
97             Future::Utils::repeat
98 7     29   2305 {
  29         1035  
99             $self->{server_responses}->shift->on_done(
100             sub {
101             my ($response) = @_;
102             $self->handle_server_response($response);
103             return;
104 9         2099 }
105 9         47 );
106 9         21 } ## end Future::Utils::repeat
107             while => sub { 1 };
108 16     16   706  
109             $self->{stream} = IO::Async::Stream->new_for_stdio(
110 7     9   2564 autoflush => 0,
  9         486  
111             on_read => sub {
112             my $size = 0;
113              
114             return sub {
115 7     7   275122 my ($stream, $buffref, $eof) = @_;
116              
117             exit if $eof;
118 24         99236  
119             unless ($size)
120 24 50       210 {
121             return 0 unless ($$buffref =~ s/^(.*?)\r\n\r\n//s);
122 24 50       208 my $headers = $1;
123              
124 24 50       746 my %headers = map { split /: / } grep { length } split /\r\n/, $headers;
125 24         367 $size = $headers{'Content-Length'};
126             die 'no Content-Length header provided' unless $size;
127 24         170 } ## end unless ($size)
  24         252  
  24         187  
128 24         142  
129 24 100       325 return 0 if (length($$buffref) < $size);
130              
131             my $json = substr $$buffref, 0, $size, '';
132 23 50       297 $size = 0;
133              
134 23         247 my $content = decode_json $json;
135 23         57  
136             $self->handle_client_message($content);
137 23         3331 return 1;
138             };
139 23         212 }
140 23         1489 );
141 7         259  
142             $self->{loop}->add($self->{stream});
143 7         2344 $self->{loop}->add(
144             IO::Async::Signal->new(name => 'TERM',
145 7         5673 on_receipt => sub { $self->stop(0) })
146             )
147             if ($^O ne 'MSWin32');
148 4     4   17895  
149 7 50       6489 my $exit_code = $self->{loop}->run();
150              
151             return (length $exit_code) ? $exit_code : 1;
152 7         5290 } ## end sub run
153              
154 6 50       2030 {
155             my ($self, $message) = @_;
156              
157             if (length $message->{method})
158             {
159 23     23 0 59 $message = PLS::Server::Request::Factory->new($message);
160              
161 23 100       136 if (blessed($message) and $message->isa('PLS::Server::Response'))
162             {
163 19         542 $self->{server_responses}->push($message);
164             return;
165 19 100 66     677 }
166             } ## end if (length $message->{...})
167 2         30 else
168 2         416 {
169             $message = PLS::Server::Response->new($message);
170             }
171              
172             return unless blessed($message);
173 4         93  
174             if ($message->isa('PLS::Server::Request'))
175             {
176 21 50       218 $self->{client_requests}->push($message);
177             }
178 21 100       179 if ($message->isa('PLS::Server::Response'))
179             {
180 17         174 $self->{client_responses}->push($message);
181             }
182 21 100       4181  
183             return;
184 4         37 } ## end sub handle_client_message
185              
186             {
187 21         2264 my ($self, $request) = @_;
188              
189             return unless blessed($request);
190              
191             if ($request->isa('PLS::Server::Request'))
192 30     30 0 638 {
193             $self->{server_requests}->push($request);
194 30 50       235 }
195             elsif ($request->isa('Future'))
196 30 100       435 {
    50          
197             $request->on_done(
198 28         170 sub {
199             my ($request) = @_;
200              
201             $self->{server_requests}->push($request);
202             }
203             )->retain();
204 1     1   115 } ## end elsif ($request->isa('Future'...))
205             return;
206 1         14 } ## end sub send_server_request
207              
208 2         56 {
209             my ($self, $message) = @_;
210 30         5246  
211             return if (not blessed($message) or not $message->isa('PLS::Server::Message'));
212             my $json = $message->serialize();
213             my $length = length $$json;
214             $self->{stream}->write("Content-Length: $length\r\n\r\n$$json")->retain();
215 38     38 0 77  
216             return;
217 38 50 33     389 } ## end sub send_message
218 38         359  
219 38         109 {
220 38         397 my ($self, $request) = @_;
221              
222 38         9182 my $response = $request->service($self);
223              
224             if (blessed($response))
225             {
226             if ($response->isa('PLS::Server::Response'))
227 17     17 0 71 {
228             $self->{server_responses}->push($response);
229 17         391 }
230             elsif ($response->isa('Future'))
231 17 100       1550 {
232             $self->{running_futures}{$request->{id}} = $response if (length $request->{id});
233 7 100       200  
    50          
234             $response->on_done(
235 6         105 sub {
236             my ($response) = @_;
237             $self->{server_responses}->push($response);
238             }
239 1 50       19 )->on_cancel(
240             sub {
241             $self->{server_responses}->push(PLS::Server::Response::Cancelled->new(id => $request->{id}));
242             }
243 0     0   0 );
244 0         0 } ## end elsif ($response->isa('Future'...))
245             } ## end if (blessed($response)...)
246              
247             return;
248 1     1   52 } ## end sub handle_client_request
249              
250 1         19 {
251             my ($self, $response) = @_;
252              
253             my $request = $self->{pending_requests}{$response->{id}};
254 17         1660  
255             if (blessed($request) and $request->isa('PLS::Server::Request'))
256             {
257             $request->handle_response($response, $self);
258             }
259 4     4 0 29  
260             return;
261 4         34 } ## end sub handle_client_response
262              
263 4 50 33     198 {
264             my ($self, $request) = @_;
265 4         134  
266             if ($request->{notification})
267             {
268 4         197 delete $request->{notification};
269             }
270             else
271             {
272             $request->{id} = ++$self->{last_request_id};
273 29     29 0 76 $self->{pending_requests}{$request->{id}} = $request;
274             }
275 29 100       140  
276             delete $self->{running_futures}{$request->{id}} if (length $request->{id});
277 14         47 $self->send_message($request);
278             return;
279             } ## end sub handle_server_request
280              
281 15         42 {
282 15         103 my ($self, $response) = @_;
283              
284             $self->send_message($response);
285 29 100       114 return;
286 29         120 } ## end sub handle_server_response
287 29         83  
288             {
289             my ($self, $pid) = @_;
290              
291             my $timer = IO::Async::Timer::Periodic->new(
292 9     9 0 65 interval => 30,
293             on_tick => sub {
294 9         50 return if (kill 'ZERO', $pid);
295 9         31 $self->stop(1);
296             }
297             );
298             $self->{loop}->add($timer);
299              
300 5     5 0 32 $timer->start();
301              
302             return;
303             } ## end sub monitor_client_process
304              
305 0 0   0   0 {
306 0         0 my ($self, $exit_code) = @_;
307              
308 5         251 $self->{loop}->stop($exit_code);
309 5         1092  
310             return;
311 5         703 } ## end sub stop
312              
313 5         39146 1;