From 41514de2097d84e7e6068e178fa3d2c010d00c59 Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Thu, 27 Jul 2017 15:44:28 -0700 Subject: [PATCH 1/9] Add links for mojo to ease dev --- public/extern | 1 + public/static | 1 + 2 files changed, 2 insertions(+) create mode 120000 public/extern create mode 120000 public/static diff --git a/public/extern b/public/extern new file mode 120000 index 0000000..f1fdb39 --- /dev/null +++ b/public/extern @@ -0,0 +1 @@ +../extern \ No newline at end of file diff --git a/public/static b/public/static new file mode 120000 index 0000000..4dab164 --- /dev/null +++ b/public/static @@ -0,0 +1 @@ +../static \ No newline at end of file From c9a995ef1803839367398a25250f4be9f58a7077 Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Thu, 27 Jul 2017 21:57:15 -0700 Subject: [PATCH 2/9] Make web parts use new async code for pastebin. API is broken. --- lib/App/Controller/Eval.pm | 15 +++- lib/App/Controller/Paste.pm | 19 +++-- lib/App/Model/Eval.pm | 156 +++++++++++++++++++++++++----------- 3 files changed, 133 insertions(+), 57 deletions(-) diff --git a/lib/App/Controller/Eval.pm b/lib/App/Controller/Eval.pm index 3bcbf9f..db4885b 100644 --- a/lib/App/Controller/Eval.pm +++ b/lib/App/Controller/Eval.pm @@ -23,9 +23,18 @@ sub run_eval { my $code = $data->param('code') // ''; my $language = $data->param('language') // 'perl'; - my $output = $self->eval->get_eval(undef, $code, $language); + $self->delay(sub { + my $delay = shift; + $self->eval->get_eval(undef, $code, [$language], $delay->begin(0,1)); - $self->render(json => {evalout => $output}); -}; + return 1; + }, + sub { + my $delay = shift; + my ($output) = @_; + + $self->render(json => {evalout => $output}); + }) +} 1; diff --git a/lib/App/Controller/Paste.pm b/lib/App/Controller/Paste.pm index e022319..eddeac9 100644 --- a/lib/App/Controller/Paste.pm +++ b/lib/App/Controller/Paste.pm @@ -80,13 +80,20 @@ sub get_paste { my $row = $c->paste->get_paste($pasteid); if ($row) { - $c->stash($row); - $c->stash({language => $c->languages->get_language_hash->{$row->{language}}}); - $c->stash({page_tmpl => 'viewer.html'}); - $c->stash({eval => $c->eval->get_eval($pasteid, $row->{paste}, $row->{language})}); - $c->stash({paste_id => $pasteid}); + $c->delay(sub { + my $delay = shift; - $c->render('page'); + $c->eval->get_eval($pasteid, $row->{paste}, [$row->{language}], $delay->begin(0,1)); + }, sub { + my ($delay, $evalout) = @_; + $c->stash($row); + $c->stash({language => $c->languages->get_language_hash->{$row->{language}}}); + $c->stash({page_tmpl => 'viewer.html'}); + $c->stash({paste_id => $pasteid}); + $c->stash({eval => $evalout}); + + $c->render('page'); + }); } else { return $c->reply->not_found; } diff --git a/lib/App/Model/Eval.pm b/lib/App/Model/Eval.pm index ec1b53b..8b7cde8 100644 --- a/lib/App/Model/Eval.pm +++ b/lib/App/Model/Eval.pm @@ -10,75 +10,135 @@ use App::EvalServerAdvanced::Protocol; use App::Config; use App::Memcached; +use Future::Mojo; +use Mojo::IOLoop; has cfg => sub {App::Config::get_config('evalserver')}; +our $id = 0; # global id count for evals + sub get_eval { - my ($self, $paste_id, $code, $lang) = @_; - - if ($paste_id && (my $cached = $memd->get($paste_id))) { + my ($self, $paste_id, $code, $langs, $callback) = @_; + + if ($paste_id && (my $cached = $memd->get($paste_id))) { # TODO make this use sereal to store objects return $cached; } else { + # connect to server + my %futures; - $lang //= "perl"; - return undef if ($lang eq 'text'); + my $server = $self->eval_connect(sub { + my ($loop, $err, $stream) = @_; - my $str = eval {$self->do_singleeval($lang, $code)}; + my $reader = $self->get_eval_reader($stream); + my %output; - return "ERROR: evalserver broken: $@" if $@; + for my $lang (@$langs) { + if ($lang eq 'text') { + next; + } else { + my $future = $self->async_eval($stream, $reader, $lang, $code); + $futures{$lang} = $future; - $memd->set($paste_id, $str) if ($paste_id); + $future->on_done(sub { + my ($out) = @_; - return $str; + print "Future is done\n"; + + $output{$lang} = $out; + delete $futures{$lang}; + + if (!keys %futures) { # I'm the last one + print "Calling memset\n"; + $memd->set($paste_id, \%output) if ($paste_id); + print "Returning output to delay\n"; + use Data::Dumper; + print Dumper(\%output); + $callback->(\%output); + } + }); + } + } + }); } } -sub do_singleeval { - my ($self, $type, $code) = @_; +sub eval_connect { + my ($self, $cb) = @_; - my $socket = IO::Socket::INET->new(PeerAddr => $self->cfg->{server} //'localhost', PeerPort => $self->cfg->{port} //14401) - or die "error: cannot connect to eval server"; + my $loop = Mojo::IOLoop->singleton; - my $eval_obj = {language => $type, files => [{filename => '__code', contents => $code, encoding => "utf8"}], prio => {pr_realtime=>{}}, sequence => 1, encoding => "utf8"}; + my $socket = $loop->client({address => $self->cfg->{server} // 'localhost', port => $self->cfg->{port} // 14401}, $cb); - $socket->autoflush(1); - print $socket encode_message(eval => $eval_obj); + return $socket; +} - my $buf = ''; - my $data = ''; - my $resultstr = "Failed to read a message"; +sub async_eval { + my ($self, $stream, $reader, $lang, $code) = @_; - my $message = $self->read_message($socket); + my $loop = Mojo::IOLoop->singleton; + my $future = Future::Mojo->new($loop); - if (ref($message) =~ /Warning$/) { - return $message->message; - } else { - return $message->get_contents; + my $seq = $id++; + my $eval_obj = {language => $lang, + files => [ + {filename => '__code', contents => $code, encoding => "utf8"} + ], + prio => {pr_deadline => {}}, + sequence => $seq, + encoding => "utf8"}; + + my $message = encode_message(eval => $eval_obj); + + $reader->($seq, $future); + $stream->write($message); + + return ($seq => $future); +} + +sub get_eval_reader { + my ($self, $stream) = @_; + + my %futures; + my %warnings; + + my $buf; + my $out; + + $stream->on(read => sub { + my ($stream, $bytes) = @_; + + print "Reading bytes\n"; + + $buf = $buf . $bytes; + my ($res, $message, $nbuf) = decode_message($buf); + $buf = $nbuf; + + if ($message) { + + my $type = ref ($message); + $type =~ s/^App::EvalServerAdvanced::Protocol:://; + + my $seq = $message->sequence; + + if ($type eq 'Warning') { + push @{$warnings{$seq}}, $message->message; + } elsif ($type eq 'EvalResponse') { + print "Got eval response for $seq\n"; + my $output = $message->get_contents; + + my $warnings = join ' ', @{$warnings{$seq} || []}; + + $futures{$seq}->done($output); + print "Future is done: $output\n"; + } + } + }); + + return sub { + print "WTF\n"; + my ($seq, $future) = @_; + $futures{$seq} = $future; } } - -sub read_message { - my ($self, $socket) = @_; - - my $header; - $socket->read($header, 8) or die "Couldn't read from socket"; - - my ($reserved, $length) = unpack "NN", $header; - - die "Invalid packet" unless $reserved == 1; - - my $buffer; - $socket->read($buffer, $length) or die "Couldn't read from socket2"; - - my ($res, $message, $nbuf) = decode_message($header . $buffer); - - - die "Data left over in buffer" unless $nbuf eq ''; - die "Couldn't decode packet" unless $res; - - return $message; -} - - 1; From 9f491dba389232b04c96e8da26bccb2e20822d6e Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Thu, 27 Jul 2017 22:02:44 -0700 Subject: [PATCH 3/9] Fix the API --- lib/App/Controller/Apiv1.pm | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/App/Controller/Apiv1.pm b/lib/App/Controller/Apiv1.pm index da254e3..4017263 100644 --- a/lib/App/Controller/Apiv1.pm +++ b/lib/App/Controller/Apiv1.pm @@ -28,16 +28,27 @@ sub api_get_paste { my $row = $c->paste->get_paste($pasteid); if ($row) { - my $data = { - paste => $row->{paste}, - when => $row->{when}, - username => $row->{who}, - description => $row->{desc}, - language => $row->{language}, - output => $c->eval->get_eval($pasteid, $row->{paste}, $row->{language}) - }; + $c->delay(sub { + my ($delay) = @_; + $c->eval->get_eval($pasteid, $row->{paste}, [$row->{language}], $delay->begin(0, 1)) + }, + sub { + my ($delay, $output_hr) = @_; - $c->render(json => $data); + my ($output_lang) = keys %$output_hr; # grab a random output value, should be the first one since multilang support isn't working yet + my ($output) = $output_hr->{$output_lang}; + my $data = { + paste => $row->{paste}, + when => $row->{when}, + username => $row->{who}, + description => $row->{desc}, + language => $output_lang, + output => $output + warning => "If this was multi-language paste, you just got a random language", + }; + + $c->render(json => $data); + }); } else { # 404 return $c->reply->not_found; From 2cc7c6ab75cd07813ddd16aa640ef1d17ff2d2d2 Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Thu, 27 Jul 2017 22:03:12 -0700 Subject: [PATCH 4/9] Add new module dep --- cpanfile | 1 + 1 file changed, 1 insertion(+) diff --git a/cpanfile b/cpanfile index 9c4bca5..403ade7 100644 --- a/cpanfile +++ b/cpanfile @@ -10,3 +10,4 @@ requires 'Mojolicious::Lite'; requires 'Mojolicious::Plugin::TtRenderer'; requires 'Mojolicious::Plugin::BlogSpam'; requires 'App::EvalServerAdvanced::Protocol'; +requires 'Future::Mojo'; From fae3e21403dd4c06c42443079f9448aa24bf1db6 Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Fri, 28 Jul 2017 01:41:01 -0400 Subject: [PATCH 5/9] Better multilanguage support on frontend, and fix backend bugs --- lib/App/Controller/Apiv1.pm | 2 +- lib/App/Model/Eval.pm | 2 +- templates/editor.html | 15 ++++++++++++++- templates/viewer.html | 6 ++++-- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/App/Controller/Apiv1.pm b/lib/App/Controller/Apiv1.pm index 4017263..978e16a 100644 --- a/lib/App/Controller/Apiv1.pm +++ b/lib/App/Controller/Apiv1.pm @@ -43,7 +43,7 @@ sub api_get_paste { username => $row->{who}, description => $row->{desc}, language => $output_lang, - output => $output + output => $output, warning => "If this was multi-language paste, you just got a random language", }; diff --git a/lib/App/Model/Eval.pm b/lib/App/Model/Eval.pm index 8b7cde8..2b57fa7 100644 --- a/lib/App/Model/Eval.pm +++ b/lib/App/Model/Eval.pm @@ -21,7 +21,7 @@ sub get_eval { my ($self, $paste_id, $code, $langs, $callback) = @_; if ($paste_id && (my $cached = $memd->get($paste_id))) { # TODO make this use sereal to store objects - return $cached; + $callback->($cached); } else { # connect to server my %futures; diff --git a/templates/editor.html b/templates/editor.html index 048b064..7ca4edb 100755 --- a/templates/editor.html +++ b/templates/editor.html @@ -268,7 +268,20 @@ data: {code: code, language: language}, dataType: "json", success: function(data, status) { - $('#eval').text(data.evalout); + console.log("data out", data); + var keys = Object.keys(data.evalout); + var outputarr = []; + + if (keys.length > 1) { + outputarr = $.map(data.evalout, function(output, lang) { + return "[[ "+lang+" ]]\n"+output+"\n\n"; + }); + } else { + outputarr = [data.evalout[keys[0]]]; + } + console.log("outputarr", outputarr); + + $('#eval').text(Array.join(outputarr, "\n")); } }); }); diff --git a/templates/viewer.html b/templates/viewer.html index 88f78de..79e45f4 100755 --- a/templates/viewer.html +++ b/templates/viewer.html @@ -69,12 +69,14 @@

           
         
+	[% FOR lang IN eval.keys %]
         
-

Program Output:

-
[% eval | html %]
+

Program Output as [% lang %]:

+
[% eval.$lang | html %]
+ [% END %] From 51e609471732cf27476bc602836fc999d2b5839c Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Fri, 28 Jul 2017 02:05:15 -0400 Subject: [PATCH 6/9] Making it work mostly, need wireshark. --- lib/App/Model/Eval.pm | 70 ++++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/lib/App/Model/Eval.pm b/lib/App/Model/Eval.pm index 2b57fa7..209f1c4 100644 --- a/lib/App/Model/Eval.pm +++ b/lib/App/Model/Eval.pm @@ -16,11 +16,31 @@ use Mojo::IOLoop; has cfg => sub {App::Config::get_config('evalserver')}; our $id = 0; # global id count for evals +my %_futures; + +sub _adopt_future { + my ($self, $id, $future) = @_; + + $_futures{$id} = $future; + + $future->on_ready(sub { + print "Cleaning up $id\n"; + delete $_futures{$id}; + }) +} + sub get_eval { my ($self, $paste_id, $code, $langs, $callback) = @_; + print "Entering\n"; - if ($paste_id && (my $cached = $memd->get($paste_id))) { # TODO make this use sereal to store objects + if (@$langs == 1 && $langs->[0] eq "evalall") { + $langs = [qw/perl perl5.26 perl5.24 perl5.22 perl5.20 perl5.18 perl5.16 perl5.14 perl5.12 perl5.10 perl5.8 perl5.6/]; + } + + use Data::Dumper; + print "Languages! ", Dumper($langs); + if ($paste_id && (my $cached = $memd->get($paste_id))) { $callback->($cached); } else { # connect to server @@ -34,7 +54,8 @@ sub get_eval { for my $lang (@$langs) { if ($lang eq 'text') { - next; + $callback->(""); + return; } else { my $future = $self->async_eval($stream, $reader, $lang, $code); $futures{$lang} = $future; @@ -42,11 +63,13 @@ sub get_eval { $future->on_done(sub { my ($out) = @_; - print "Future is done\n"; + print "Future is done for $lang\n"; $output{$lang} = $out; delete $futures{$lang}; + print "remaining, ", Dumper(keys %futures); + if (!keys %futures) { # I'm the last one print "Calling memset\n"; $memd->set($paste_id, \%output) if ($paste_id); @@ -79,6 +102,8 @@ sub async_eval { my $future = Future::Mojo->new($loop); my $seq = $id++; + + $self->_adopt_future($seq, $future); my $eval_obj = {language => $lang, files => [ {filename => '__code', contents => $code, encoding => "utf8"} @@ -110,33 +135,38 @@ sub get_eval_reader { print "Reading bytes\n"; $buf = $buf . $bytes; - my ($res, $message, $nbuf) = decode_message($buf); - $buf = $nbuf; + my ($res, $message, $nbuf); + do { + ($res, $message, $nbuf) = decode_message($buf); + $buf = $nbuf; - if ($message) { + if ($message) { - my $type = ref ($message); - $type =~ s/^App::EvalServerAdvanced::Protocol:://; + my $type = ref ($message); + $type =~ s/^App::EvalServerAdvanced::Protocol:://; - my $seq = $message->sequence; + my $seq = $message->sequence; - if ($type eq 'Warning') { - push @{$warnings{$seq}}, $message->message; - } elsif ($type eq 'EvalResponse') { - print "Got eval response for $seq\n"; - my $output = $message->get_contents; + if ($type eq 'Warning') { + push @{$warnings{$seq}}, $message->message; + } elsif ($type eq 'EvalResponse') { + print "Got eval response for $seq\n"; + my $output = $message->get_contents; - my $warnings = join ' ', @{$warnings{$seq} || []}; + my $warnings = join ' ', @{$warnings{$seq} || []}; - $futures{$seq}->done($output); - print "Future is done: $output\n"; - } - } + $futures{$seq}->done($output); + print "Future is done: $output\n"; + } + }; + + print Dumper($res, length($buf)); + } while ($res); }); return sub { - print "WTF\n"; my ($seq, $future) = @_; + print "Registering $seq\n"; $futures{$seq} = $future; } } From 5f813c15e1d657f2e7bac8d5f5fa6642ac60e7b6 Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Thu, 27 Jul 2017 23:17:31 -0700 Subject: [PATCH 7/9] More debugging, but not quite right --- lib/App/Controller/Eval.pm | 4 ++++ lib/App/Model/Eval.pm | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/App/Controller/Eval.pm b/lib/App/Controller/Eval.pm index db4885b..41505f4 100644 --- a/lib/App/Controller/Eval.pm +++ b/lib/App/Controller/Eval.pm @@ -20,6 +20,10 @@ sub run_eval { my ($self) = @_; my $data = $self->req->body_params; + + + $self = $self->inactivity_timeout(3600); + my $code = $data->param('code') // ''; my $language = $data->param('language') // 'perl'; diff --git a/lib/App/Model/Eval.pm b/lib/App/Model/Eval.pm index 209f1c4..10b4c78 100644 --- a/lib/App/Model/Eval.pm +++ b/lib/App/Model/Eval.pm @@ -112,6 +112,9 @@ sub async_eval { sequence => $seq, encoding => "utf8"}; + use Data::Dumper; + print Dumper($eval_obj); + my $message = encode_message(eval => $eval_obj); $reader->($seq, $future); @@ -162,6 +165,8 @@ sub get_eval_reader { print Dumper($res, length($buf)); } while ($res); + + return 0; }); return sub { From 668a41aea64b9efaf2a3ec68660a5f3e40cb8a1f Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Thu, 27 Jul 2017 23:33:33 -0700 Subject: [PATCH 8/9] Make evalall work --- lib/App/Model/Eval.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/App/Model/Eval.pm b/lib/App/Model/Eval.pm index 10b4c78..296c9d1 100644 --- a/lib/App/Model/Eval.pm +++ b/lib/App/Model/Eval.pm @@ -108,7 +108,7 @@ sub async_eval { files => [ {filename => '__code', contents => $code, encoding => "utf8"} ], - prio => {pr_deadline => {}}, + prio => {pr_realtime => {}}, sequence => $seq, encoding => "utf8"}; @@ -142,6 +142,7 @@ sub get_eval_reader { do { ($res, $message, $nbuf) = decode_message($buf); $buf = $nbuf; + print Dumper($message); if ($message) { @@ -163,7 +164,6 @@ sub get_eval_reader { } }; - print Dumper($res, length($buf)); } while ($res); return 0; From 5705eea416877139ba488412ec4b48eb43a6d8cd Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Thu, 27 Jul 2017 23:34:47 -0700 Subject: [PATCH 9/9] Add evalall --- lib/App/Model/Languages.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/App/Model/Languages.pm b/lib/App/Model/Languages.pm index 5f39a63..fd2d674 100644 --- a/lib/App/Model/Languages.pm +++ b/lib/App/Model/Languages.pm @@ -22,6 +22,7 @@ my @langs = ( {name => "perl5.10", mode => "perl", description => "Perl 5.10"}, {name => "perl5.8", mode => "perl", description => "Perl 5.8"}, {name => "perl5.6", mode => "perl", description => "Perl 5.6"}, + {name => "evalall", mode => "perl", description => "Perl (EvalAll)"}, {name => "perl5.5", mode => "perl", description => "Perl 5.5"}, {name => "perl4", mode => "perl", description => "Perl 4.0.36"}, {name => "perl3", mode => "perl", description => "Perl 3.0.1.10_44"},