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   713818 use warnings;
  9         90  
  9         199  
4 9     9   37  
  9         9  
  9         164  
5             use Future;
6 9     9   4557 use Future::Queue;
  9         85262  
  9         235  
7 9     9   2688 use Future::Utils;
  9         2541  
  9         225  
8 9     9   3495 use IO::Async::Loop;
  9         15486  
  9         329  
9 9     9   4965 use IO::Async::Signal;
  9         231600  
  9         289  
10 9     9   3743 use IO::Async::Stream;
  9         31704  
  9         243  
11 9     9   4206 use IO::Async::Timer::Periodic;
  9         157858  
  9         283  
12 9     9   3348 use IO::Handle;
  9         13859  
  9         237  
13 9     9   40 use Scalar::Util qw(blessed);
  9         17  
  9         254  
14 9     9   45  
  9         10  
  9         271  
15             use PLS::JSON;
16 9     9   2620 use PLS::Server::Request::Factory;
  9         25  
  9         356  
17 9     9   4559 use PLS::Server::Response;
  9         139  
  9         1050  
18 9     9   55 use PLS::Server::Response::Cancelled;
  9         25  
  9         171  
19 9     9   3227  
  9         18  
  9         12979  
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 63043157 return
43             bless {
44             loop => IO::Async::Loop->new(),
45 7         854 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 1104 $self->{client_responses} = Future::Queue->new();
56             $self->{server_requests} = Future::Queue->new();
57 7         550 $self->{server_responses} = Future::Queue->new();
58 7         607  
59 7         387 Future::Utils::repeat
60 7         413 {
61             $self->{client_requests}->shift->on_done(
62             sub {
63             my ($request) = @_;
64              
65             $self->handle_client_request($request);
66 17         3590 return;
67             }
68 17         112 );
69 17         226 } ## end Future::Utils::repeat
70             while => sub { 1 };
71 24     24   1699  
72             Future::Utils::repeat
73 7     17   894 {
  17         904  
74             $self->{client_responses}->shift->on_done(
75             sub {
76             my ($response) = @_;
77              
78             $self->handle_client_response($response);
79 4         955 return;
80             }
81 4         43 );
82 4         205 } ## end Future::Utils::repeat
83             while => sub { 1 };
84 11     11   703  
85             Future::Utils::repeat
86 7     4   5088 {
  4         533  
87             $self->{server_requests}->shift->on_done(
88             sub {
89             my ($request) = @_;
90             $self->handle_server_request($request);
91             return;
92 29         4169 }
93 29         105 );
94 29         59 } ## end Future::Utils::repeat
95             while => sub { 1 };
96 36     36   941  
97             Future::Utils::repeat
98 7     29   1962 {
  29         890  
99             $self->{server_responses}->shift->on_done(
100             sub {
101             my ($response) = @_;
102             $self->handle_server_response($response);
103             return;
104 9         2227 }
105 9         74 );
106 9         24 } ## end Future::Utils::repeat
107             while => sub { 1 };
108 16     16   579  
109             $self->{stream} = IO::Async::Stream->new_for_stdio(
110 7     9   1881 autoflush => 0,
  9         385  
111             on_read => sub {
112             my $size = 0;
113              
114             return sub {
115 7     7   232305 my ($stream, $buffref, $eof) = @_;
116              
117             exit if $eof;
118 24         102596  
119             unless ($size)
120 24 50       208 {
121             return 0 unless ($$buffref =~ s/^(.*?)\r\n\r\n//s);
122 24 50       188 my $headers = $1;
123              
124 24 50       588 my %headers = map { split /: / } grep { length } split /\r\n/, $headers;
125 24         293 $size = $headers{'Content-Length'};
126             die 'no Content-Length header provided' unless $size;
127 24         145 } ## end unless ($size)
  24         281  
  24         148  
128 24         109  
129 24 100       252 return 0 if (length($$buffref) < $size);
130              
131             my $json = substr $$buffref, 0, $size, '';
132 23 50       259 $size = 0;
133              
134 23         182 my $content = decode_json $json;
135 23         53  
136             $self->handle_client_message($content);
137 23         3102 return 1;
138             };
139 23         206 }
140 23         1479 );
141 7         219  
142             $self->{loop}->add($self->{stream});
143 7         1785 $self->{loop}->add(
144             IO::Async::Signal->new(name => 'TERM',
145 7         4985 on_receipt => sub { $self->stop(0) })
146             )
147             if ($^O ne 'MSWin32');
148 4     4   18204  
149 7 50       5125 my $exit_code = $self->{loop}->run();
150              
151             return (length $exit_code) ? $exit_code : 1;
152 7         4757 } ## end sub run
153              
154 6 50       1615 {
155             my ($self, $message) = @_;
156              
157             if (length $message->{method})
158             {
159 23     23 0 171 $message = PLS::Server::Request::Factory->new($message);
160              
161 23 100       151 if (blessed($message) and $message->isa('PLS::Server::Response'))
162             {
163 19         410 $self->{server_responses}->push($message);
164             return;
165 19 100 66     552 }
166             } ## end if (length $message->{...})
167 2         37 else
168 2         451 {
169             $message = PLS::Server::Response->new($message);
170             }
171              
172             return unless blessed($message);
173 4         102  
174             if ($message->isa('PLS::Server::Request'))
175             {
176 21 50       132 $self->{client_requests}->push($message);
177             }
178 21 100       190 if ($message->isa('PLS::Server::Response'))
179             {
180 17         122 $self->{client_responses}->push($message);
181             }
182 21 100       3764  
183             return;
184 4         71 } ## end sub handle_client_message
185              
186             {
187 21         1904 my ($self, $request) = @_;
188              
189             return unless blessed($request);
190              
191             if ($request->isa('PLS::Server::Request'))
192 30     30 0 581 {
193             $self->{server_requests}->push($request);
194 30 50       155 }
195             elsif ($request->isa('Future'))
196 30 100       438 {
    50          
197             $request->on_done(
198 28         177 sub {
199             my ($request) = @_;
200              
201             $self->{server_requests}->push($request);
202             }
203             )->retain();
204 1     1   87 } ## end elsif ($request->isa('Future'...))
205             return;
206 1         19 } ## end sub send_server_request
207              
208 2         77 {
209             my ($self, $message) = @_;
210 30         4277  
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 103  
216             return;
217 38 50 33     317 } ## end sub send_message
218 38         375  
219 38         64 {
220 38         335 my ($self, $request) = @_;
221              
222 38         8166 my $response = $request->service($self);
223              
224             if (blessed($response))
225             {
226             if ($response->isa('PLS::Server::Response'))
227 17     17 0 47 {
228             $self->{server_responses}->push($response);
229 17         436 }
230             elsif ($response->isa('Future'))
231 17 100       1339 {
232             $self->{running_futures}{$request->{id}} = $response if (length $request->{id});
233 7 100       208  
    50          
234             $response->on_done(
235 6         95 sub {
236             my ($response) = @_;
237             $self->{server_responses}->push($response);
238             }
239 1 50       16 )->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   34 } ## end sub handle_client_request
249              
250 1         19 {
251             my ($self, $response) = @_;
252              
253             my $request = $self->{pending_requests}{$response->{id}};
254 17         1387  
255             if (blessed($request) and $request->isa('PLS::Server::Request'))
256             {
257             $request->handle_response($response, $self);
258             }
259 4     4 0 19  
260             return;
261 4         47 } ## end sub handle_client_response
262              
263 4 50 33     136 {
264             my ($self, $request) = @_;
265 4         151  
266             if ($request->{notification})
267             {
268 4         236 delete $request->{notification};
269             }
270             else
271             {
272             $request->{id} = ++$self->{last_request_id};
273 29     29 0 53 $self->{pending_requests}{$request->{id}} = $request;
274             }
275 29 100       111  
276             delete $self->{running_futures}{$request->{id}} if (length $request->{id});
277 14         42 $self->send_message($request);
278             return;
279             } ## end sub handle_server_request
280              
281 15         29 {
282 15         103 my ($self, $response) = @_;
283              
284             $self->send_message($response);
285 29 100       99 return;
286 29         84 } ## end sub handle_server_response
287 29         54  
288             {
289             my ($self, $pid) = @_;
290              
291             my $timer = IO::Async::Timer::Periodic->new(
292 9     9 0 72 interval => 30,
293             on_tick => sub {
294 9         65 return if (kill 'ZERO', $pid);
295 9         32 $self->stop(1);
296             }
297             );
298             $self->{loop}->add($timer);
299              
300 5     5 0 20 $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         314 $self->{loop}->stop($exit_code);
309 5         854  
310             return;
311 5         586 } ## end sub stop
312              
313 5         30283 1;