0

I am new to perl plack/psgi. I want to access a subroutine within the perl plack/psgi loop, but it looks like if the subroutine is not being executed. Every parent variable like $number should being passed automatically like when writing a regular perl script. Do I miss something here, is that possible?

..
my $app = sub {

  my $number = 10;

  &count_number;
  sub count_number {
    $number +=10;
  }


  return ['200',[  'Content-Type' => 'application/json' ],
  [ "{\"number\":$number} ]];     

}
.. 

10 is being returned instead of 20 :(

JOhnlw009a
  • 1,012
  • 1
  • 7
  • 12
  • 1
    You shouldn't nest one `sub` declaration inside another, that's usually wrong. Also, `my $number` is declared in the `sub`, so it will disappear every time the `sub` finishes executing. But more importantly: What do you want to count? Is it a global counter, a per-session value, etc.? Also, how are you running your app? If the server is running multiple copies of the app (e.g. threads/fork), and you want the counter to be global, that needs to be implemented differently (e.g. a database or other global store). – haukex Apr 29 '18 at 11:43
  • Thanks so far. Do I have to pass the variables instead like $number = &count_number(%args) - that would complicate things up. Yesterday I already thought about multiprocessing environments, also including a load balancer. What would be a good internal (just having one external user cookie, like session id) storage solution for multiple threads and servers? – JOhnlw009a Apr 29 '18 at 11:53
  • 1
    I think at this point all I can say is: it depends on what you need, which you haven't really described fully. I've used [Starman](https://metacpan.org/release/Starman), and heard good things about [nginx](https://www.nginx.com/)+[uWSGI](http://uwsgi-docs.readthedocs.io/en/latest/Perl.html). Mojolicious supports [sessions](https://mojolicious.org/perldoc/Mojolicious/Guides/Tutorial#Sessions). [See also](https://stackoverflow.com/a/9411254). – haukex Apr 29 '18 at 12:14
  • 1
    @hauk: Ordinarily, `$number` would be prevented from disappearing by the way Perl's reference counting works. Even after subroutine `$app` exits, `$number` still has a reference from within `count_number` so it is not removed. This isn't working here because subroutine `count_number` is created at compile time when there is no `$number` variable to refer to. – Borodin Apr 29 '18 at 12:18
  • @Borodin Yes, that's that good point, thanks for the correction. Although none of this really helps the OP yet if the Plack app is being run in a multithreaded or forked server :-) – haukex Apr 29 '18 at 12:21
  • Yes, I am using uWSGI and nginx. What would be a good way storing data when using multithreading? I don't want to select and update a session table every time though, reflecting on a high traffic website. – JOhnlw009a Apr 29 '18 at 12:31
  • @JOhn: Does my answer below not help at all? – Borodin Apr 29 '18 at 12:34
  • Above, I gave you [this link](https://stackoverflow.com/a/9411254), which links to [`Cache::FastMmap`](https://metacpan.org/pod/Cache::FastMmap), whose documentation includes several other similar modules. – haukex Apr 29 '18 at 12:37
  • Didn't see it. Thanks a lot! – JOhnlw009a Apr 29 '18 at 12:39

2 Answers2

1

If I fix the quotes on the string in the return statement (you are missing a closing double-quote) then I get the warning

Variable "$number" is not available at source_file.pl line 7.

The reason is that lexical values $app and $number are defined at run time, whereas the subroutine count_number is defined much earlier during compilation

The solution is to defer definition of count_number to run time by making it an anonymous subroutine instead. The call $count_number->() also needs to be moved to after the definition

my $app = sub {

    my $number = 10;

    my $count_number = sub {
        $number +=10;
    };

    $count_number->();

    return [
        '200',
        [ 'Content-Type' => 'application/json' ],
        [ "{\"number\":$number}" ]
    ];     
};


use Data::Dumper;
print Dumper $app->();

output

$VAR1 = [
            '200',
            [
                'Content-Type',
                'application/json'
            ],
            [
                '{"number":20}'
            ]
        ];

There is a related warning

Variable "$number" will not stay shared

with a similar solution. You can read about both in perldoc perldiag. The messages are listed and described in alphabetical order

Borodin
  • 126,100
  • 9
  • 70
  • 144
0

The my operator has two effects:

  • at compile time, it introduces a scalar variable.
  • at run time, it creates a fresh scalar object for that variable.

Essentially, these two scalars are different variables, although they have the same name.

The sub name { ... } operator only has a compile time effect. It assigns the subroutine at compile time to the given name. So when the sub is compiled, it sees the original compile-time variable, not the run-time variable which is created much later.

Therefore, you should not nest named subs. In fact, if you use warnings you get a warning about this: “Variable "$number" will not stay shared”.

You have two options:

  • You can use a closure that sees the runtime variable. This uses anonymous subroutines:

    ...
    my $number = 10;
    my $count_number = sub {
      $number += 10;
    };
    
    $count_number->();
    
    ...
    
  • Or, you pass the values as explicit parameters to a separate subroutine. Yes, that does complicate things a bit, but it also keeps separate things separate. Clear data flow is a characteristic of good design.

amon
  • 57,091
  • 2
  • 89
  • 149
  • Clear data flow is totally appreciated. When when having the same code fragment 10 times in the code, it would be easier to have it just once in order to update parts of the code. I'll try your code now. – JOhnlw009a Apr 29 '18 at 12:39