February 21st, 2007 by depesz | Tags: | 2 comments »
Did it help? If yes - maybe you can help me?

a dziś napiszę o tym jakim to łosiem można być. niechcący.

na potrzeby jednego z projektów napisałem własnego orm'a (object-relationship mapper). nie znacie? takie cos co pozwala widziec rekordy z tabel jako obiekty. ogólnie – każdy orm jest bez sensu. mój tym bardziej, ale służył do prostego celu – uproszczenia robienia eksportów.

działał.

do czasu.

ostatnio na jednej z maszyn eksporter zaczął zżerać cały ram. calutki. i wywalać maszynę. usiadłem do debugowania. i oto co ujrzałem:

...
sub _table { my $self = shift; return $self->{ 'table_name' } }
sub _db { my $self = shift; return $self->{ 'db' } }
sub _log { my $self = shift; return $self->{ 'log' } }
sub _refresh {
my $self = shift;
my $sql = sprintf 'SELECT * FROM %s WHERE id = ?', $self->_table;
my $record = $self->_db->get_single_record( $sql, $self->{ 'data' }->{ 'id' } => 'INT8' );
if ( $record ) {
$self->{ 'data' } = $record;
$self->{ 'fetched' } = 1;
return;
}
$self->log->critical(
"Cannot refresh record data from table " . $self->_table . " for id = " . $self->{ 'data' }->{ 'id' } );
croak( "DB Error" );
}
sub _get {
my $self = shift;
my ( $field ) = @_;
$self->_refresh unless $self->{ 'fetched' };
return $self->{ 'data' }->{ $field };
}
sub AUTOLOAD {
my $self = shift;
my $method = $AUTOLOAD;
$method =~ s/.*:://;
return unless $method =~ m{ \A [a-z][a-z0-9_]* \z }xmso;
return $self->_get( $method );
}

zasada działania bardzo prosta – jeśli mam obiekt klasy dziedziczącej z tego orm'a, i wykonam na nim metodę:

$obiekt->jakies_pole

(gdzie jakies_pole nie jest nazwa istniejacej metody), to request trafi do autoloada, który wywoła _get. _get sprawdzi czy obiekt załadował z bazy dane. jak tak – zwróci odpowiednie pole i po sprawie.

a co jeśli nie?

wtedy kod trafia na _refresh(). refresh odczytuje cały rekord z bazy w oparciu o id (które musi być). fajne.

jedno pytanie: co się stanie gdy rekordu w bazie nie będzie?

tradycyjna odpowiedź: kod zaloguje informacje o błędzie i wykona croak(). czyli taki die.

ale nie. niestety. złośliwy los i brak dobrych oczu spowodował, że napisałem $self->log->(), podczas gdy obiekt loggera jest dostępny przed $self->_log.

efekt? metody log nie ma. trafiamy na autoloada. autoload odsyła do _get'a, _get do _refresha. _refresh znowu nie znajduje rekordu, więc … i kółeczko się zamyka.

nieskończona rekurencja, 3 giga zużytej pamięci, kilka godzin pracy paru osób. przez brak jednego “_".

a czemu o tym piszę? abym pamiętał. i sprawdzał kod. i bym miał okazję się pochwalić jakie “fajne" błędy potrafię wygenerować, a potem wykryć. i pochwalić “perl -d" – debugger jest wkurzający, mało sympatyczny. i ratuje d… jak oczy zawiodą.

aha. i jak fajnie wygląda “T" pod debuggerem po np. 50 przebiegach tej rekurencji 🙂 (T == stack trace).

  1. 2 comments

  2. # gaber
    Feb 22, 2007

    Koniec X-Files z maszyna… Ufff.

  3. Apr 24, 2007

    Współczuję… miałem przez takie malutkie drobiazgi dywanik nieraz u szefa… aż w krytycznych chwilach dochodziło do tego, że przed updatem na serwery produkcyjne… człowiek siedział zawieszony… palec nad enterem i refleksja… puścić, czy jeszcze potestować… ach 🙂 Codzienność programisty to ryzyko wpadki 🙂

Leave a comment