Hypnotoad and gone SQL

49 views
Skip to first unread message

Alexander Lunev

unread,
May 4, 2017, 4:00:15 AM5/4/17
to Mojolicious
Hello, everyone!
I'm having a problem with starting my app via hypnotoad. While in simple daemon (or morbo) mode everything works fine, app started by hypnotoad is failed to connect to SQL. I read https://github.com/kraih/mojo/wiki/Hypnotoad-prefork-web-server and tried to use one of recipe: DBIx::Connector instead of usual DBI->connect, with no difference: app is still can't connect to SQL in prefork mode.

The other recipe (with has => dbh) is quite hard to implement without rewriting all parts of the project, and therefore i decided first  to ask, maybe someone already solved this problem.

So app is basically consist of:

script/portmgr
lib/PortMgr.pm
lib/PortMgr/Controller/Hw.pm
lib/PortMgr/Model/Hw.pm
lib/PortMgr/Storage/PgSQL.pm

chain of calls is like:

in lib/PortMgr.pm:

$r->get('/')->to('hw#mainpage')->name('main');

then to controller:

= lib/PortMgr/Controller/Hw.pm ============================
package PortMgr::Controller::Hw;
use Mojo::Base 'Mojolicious::Controller';
use PortMgr::Model::Hw;

my $hwmodel = PortMgr::Model::Hw->new();

sub mainpage {
    my $self = shift;
    my $allhw = $hwmodel->get_hw();
    $self->render(hw => $allhw);
}
===================================================

= lib/PortMgr/Model/Hw.pm ===============================
package PortMgr::Model::Hw;
use PortMgr::Storage::PgSQL;

my $storage = PortMgr::Storage::PgSQL->new();

sub new {
    my $self = shift;
    return $self;
}

sub get_hw {
    my $self = shift;
    my $hw_id = shift;
    my $data = $storage->query(
        table => ...,
        fields => ....,
        join => ...,
        where => ...,
    );
    return $data;
}
===================================================


= lib/PortMgr/Storage/PgSQL.pm ===========================
package PortMgr::Storage::PgSQL;
use DBI;
use DBIx::Connector;

# Singleton

our $instance = undef;

sub new {
    my $class = shift;
    return $instance if defined $instance;
    my $self = {};
    $instance = bless ($self, $class);
    $self->{db} = $instance->init();
    return $instance;
}

sub init {
    my $self = shift;
    my $db = shift;
    $self->{db} = DBIx::Connector->connect("dbi:Pg:dbname=ports;host=127.0.0.10", "pgsql", "", {AutoCommit => 1, RaiseError => 1});
    if (!$self->{db}) {
        $log->error("INIT Error: " . $DBI::errstr);
        return undef;
    }
    return $self->{db};
}

sub query {
    my $self = shift;
    my $opt = { @_ };
....
....
    my $q = "SELECT $sql_fields FROM $table $sql_join $sql_where $sql_group $sql_order";

    return $self->{db}->selectall_hashref($q,$key);
}

===================================================

When in development mode or non-forked mode of hypnotoad, everything works fine, but in prefork mode I get error:

DBD::Pg::db selectall_hashref failed: server closed the connection unexpectedly.

And if I try to use recipe with "has => dbh", somehow i need to get access to $self->app in lib/PortMgr/Storage/PgSQL.pm, but it is not a Mojolicious controller class, so i need to drag $dbh from lib/PortMgr.pm to lib/PortMgr/Controller/Hw.pm to lib/PortMgr/Model/Hw.pm to lib/PortMgr/Storage/PgSQL.pm?

Is there any other solution?

--
With regards,
Alexander Lunev

Justin Hawkins

unread,
May 4, 2017, 7:10:17 AM5/4/17
to mojol...@googlegroups.com
On 4 May 2017, at 5:30 pm, Alexander Lunev <sol...@gmail.com> wrote:

DBD::Pg::db selectall_hashref failed: server closed the connection unexpectedly. 

You tried to use a database handle that had been initialised in a previous process and then forked. As you’ve realised, this doesn’t work :-)


And if I try to use recipe with "has => dbh", somehow i need to get access to $self->app in lib/PortMgr/Storage/PgSQL.pm, but it is not a Mojolicious controller class, so i need to drag $dbh from lib/PortMgr.pm to lib/PortMgr/Controller/Hw.pm to lib/PortMgr/Model/Hw.pm to lib/PortMgr/Storage/PgSQL.pm? 

Is there any other solution? 

In general, if you find it difficult to test any of your classes in isolation, you’ve got some sort of code smell. In this case, the fact that the model instantiates it’s own database handle makes it hard to test, and causes this issue.

I’d suggest instantiating your database connection in the main application, and then passing it as a parameter to your model class. No singletons. Singletons aren’t always bad, but this one is :-) The model class should store it as an attribute:

package PortMgr::Model::Hw;

use Mojo::Base qw/-base/;

has ‘storage’;


As an added benefit, now you can test more easily:

my $db = Test::PortMgr::Storage::PgSQL->new(); # db handle to test database
my $model = PortMgr::Model::Hw->new(storage = >$db);

ok($model->get_hw(), ‘can get hw’);

Cheers,

Justin


Dan Book

unread,
May 4, 2017, 1:36:25 PM5/4/17
to mojol...@googlegroups.com
More specifically, you should not store a DBI handle in your app or use it across forks. You should always store a DBIx::Connector or Mojo::Pg or similar connection pool object instead, and then retrieve a connection from it in the smallest scope needed.

--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious+unsubscribe@googlegroups.com.
To post to this group, send email to mojol...@googlegroups.com.
Visit this group at https://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.

Alexander Lunev

unread,
May 4, 2017, 4:01:04 PM5/4/17
to mojol...@googlegroups.com
Thanks, Justin! I'll try to rewrite my app with db handle stored as an
attribute, and as i see it, it'll take sometime to make everything
right.

And thanks, Dan! As a temporary solution I replaced
DBIx::Connector->connect to DBIx::Connector->new and all $self->{db}
to $self->{db}->dbh, and hypnotoad now works!
>> email to mojolicious...@googlegroups.com.
>> To post to this group, send email to mojol...@googlegroups.com.
>> Visit this group at https://groups.google.com/group/mojolicious.
>> For more options, visit https://groups.google.com/d/optout.
>
>
> --
> You received this message because you are subscribed to a topic in the
> Google Groups "Mojolicious" group.
> To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/mojolicious/s4lfO9bX6mE/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to
> mojolicious...@googlegroups.com.
> To post to this group, send email to mojol...@googlegroups.com.
> Visit this group at https://groups.google.com/group/mojolicious.
> For more options, visit https://groups.google.com/d/optout.



--
your sweet isn't ready yet

Alexander Lunev

unread,
May 10, 2017, 9:06:43 AM5/10/17
to mojol...@googlegroups.com
To close this thread as fully SOLVED, i'll post the solution to which
i came in my other project. It works with hypnotoad.

= doccer.conf =========================================
{
db => {
type => 'sqlite',
dsn => '/path/to/file.sqlite',
},
...
}

= lib/Doccer.pm =======================================
package Doccer;

has 'db' => sub {
my $self = shift;
return Doccer::Storage::Factory->new(%{ $self->plugin('Config')->{db} });
};

sub startup {
....
}

= lib/Doccer/Controller/Object.pm ==========================
package Doccer::Controller::Object;

sub show {
my $self = shift;
my $model = Doccer::Model::Object->new(storage => $self->app->db);
...
}


= lib/Doccer/Model/Object.pm =============================
package Doccer::Model::Object;

has 'storage';

sub get_something {
my $self = shift;
my $data = $self->storage->get('something');
...
Reply all
Reply to author
Forward
0 new messages