24 maj 2010

Logowanie do pliku za pomocą log4cpp

Już od dawna używam log4cpp. Dopóki rozwijałem soft, to starczało mi logowanie na konsolę.
Teraz przyszedł czas na wdrożenie i trzeba przerobić plik konfiguracyjny log4cpp tak, aby logował do pliku.

Zadanie na 10 minut - tak mi się wydawało...

Ustawiłem wszystko tak jak dla log4j (fragment dotyczący appendera do pliku):
log4j.appender.fileAppender=org.apache.log4j.FileAppender
log4j.appender.File=app.log
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%d %m %x %n

i niby loguje się do pliku, ale jego nazwa to: 'foobar'.

Ręce opadają... żadnej dokumentacji w log4cpp do konfiguratora properties... w dodatku nie są w pełni kompatybilni z log4j.

Na szczęście log4cpp to projekt opensource. Co najlepiej zastąpi dokumentację?
Oczywiście kod źródłowy! Wystarczy poszukać ciągu 'foobar' i już wiadomo:
prawidłowa opcja dla nazwy pliku to 'fileName':

log4j.appender.fileAppender=org.apache.log4j.FileAppender
log4j.appender.fileName=app.log
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%d %m %x %n

Uff! Szkoda że nie pomyślałem o kodzie źródłowym log4cpp godzinę wcześniej :-)

1 lut 2010

boost::serialization i dziwne błędy kompilacji

Błędy kompilacji kodu używającego biblioteki boost::serialization są trudne do zrozumienia.

Jednym z nich jest błąd:
/usr/include/boost/archive/detail/iserializer.hpp:570: error: no matching function for call to ‘load_wrapper(boost::archive::text_iarchive&, const std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, boost::serialization::is_wrapper<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >)’

Przyczyna jest prozaiczna: serializowalne pola nie mogą być const.
Jest to oczywiste, gdy wiemy że serializacja musi utworzyć "pusty" obiekt i potem wypełnić wartościami jego pola.

Dlatego też serializowalna klasa musi mieć:
  • konstruktor bezparametrowy
  • pola modyfikowalne (bez const)

Oczywiście z komunikatu o błędzie nie możemy tego wywnioskować... takie to już są uroki bibliotek wykorzystujących template-y...

27 sty 2010

Deklaracje zapowiadające w celu przyspieszenia kompilacji projektu

W projektach pisanych w C++ lubię korzystać z biblioteki boost.
Niestety ma ona jedną uciążliwą wadę: kod wykorzystujący boosta strasznie wolno się kompiluje. Przyczyną tego jest fakt, iż boost w dużym stopniu wykorzystuje template-y i to w bardzo zaawansowany sposób. Metaprogramy to dla kompilatora c++ nie lada wyzwanie!

W projektach składających się z kilkudziesięciu plikow źródłowych czas kompilacji całości przekracza kilka minut! Nawet przy kompilacji przyrostowej z użyciem make, po zmianie jednego z headerów w projekcie kompilacja może trwać powyżej minuty!

Jedną z technik przyspieszających kompilację jest unikanie dodawania #include do własnych headerów. Jeżeli tylko można należy wstępnie zadeklarować klasę w headerze, a właściwy #include dodać dopiero do pliku cpp.

Poniżej przykład:
Piszemy klasę Database, w której chcemy skorzystać z klasy XmlDocument. Ściślej: obiekty klasy XmlDocument będą przekazywane jako parametr w metodach klasy Database.

Jak zatem powinniśmy napisać header klasy Database.hpp:
class XmlDocument;

class Database
{
    void init( XmlDocument &document );
}

A co jeśli do obiektu XmlDocument chcemy mieć dostęp nie przez referencję, ale przez smart pointer (boost::shared_ptr)?

Żeby zdefiniować smart pointer wcale nie jest potrzebny header klasy. Także tutaj wystarczy jej deklaracja zapowiadająca:
class XmlDocument;
typedef boost::shared_ptr<XmlDocument> XmlDocumentPtr;

class Database
{
    void init( XmlDocumentPtr document );
}


Dodatkowa wskazówka: aby uniknąć duplikowania kodu, dla klas z których chcę korzystać poprzez smart pointer, tworzę osobny - dodatowy header KlasaXXXPtr.hpp

Dla przykładu header taki dla klasy XmlDocument będzie wyglądał tak:
#ifndef XMLDOCUMENTPTR_HPP_
#define XMLDOCUMENTPTR_HPP_

#ifndef XMLDOCUMENT_H_
class XmlDocument;
#endif // XMLDOCUMENT_H_

#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<XmlDocument> XmlDocumentPtr;

#endif /* XMLDOCUMENTPTR_HPP_ */
Wszędzie, gdzie chcę zadeklarować smart pointera na XmlDocument po prostu wpisuję #include "XmlDocumentPtr.hpp", natomiast w pliku cpp (np. Database.cpp) muszę jeszcze dodać #include "XmlDocument.hpp".

22 sty 2010

Deklaracje zapowiadające obiektów z biblioteki xerces-c++

Staram się ograniczać liczbę #include-ów w moich headerach.

Jeśli to możliwe wstępnie deklaruję wykorzystywaną klasę, a właściwy #include tej klasy ląduje w pliku cpp. Dzięki temu projekt znacznie szybciej się kompiluje.

Przykład: w klasie SslSession chcę korzystać z obiektów Database.
SslSession.hpp wygląda tak:
class Database;
class SslSession
{
    Database &m_db;
};
SslSession.cpp wygląda tak:
#include "Database.hpp"
Niestety wstępna deklaracja obiektów z biblioteki xerces-c++ w powyższy sposób daje następujący błąd kompilacji:
namespace xercesc
{
    class xercesc::DOMNode;
}

/usr/include/xercesc/util/XercesDefs.hpp:110: error: declaration of namespace ‘xercesc’ conflicts with
../src/modules/../lib/XmlDocumentPtr.hpp:18: error: previous declaration of namespace ‘xercesc’ here
Okazuje się, że xerces-c ma mechanizm parametrycznego włączania/wyłączania namespace (w zależności od wersji xerces-c i użytego kompilatora), który nie współgra dobrze z klasycznymi deklaracjami zapowiadającymi.

Na szczęście chłopaki z apacha zapewnili inny mechanizm pozwalający na deklararowanie ich obiektów:
#include <xercesc/util/XercesDefs.hpp>

XERCES_CPP_NAMESPACE_BEGIN
class DOMNode;
XERCES_CPP_NAMESPACE_END
Teraz wszystko ładnie się kompiluje!

18 gru 2009

Zwracanie rekordu z procedury składowanej w PostgreSQL

Jeśli z procedury/funkcji składowanej w bazie danych PostgreSQL chcemy zwrócić kilka wartości, to możemy zadeklarowąć ją jako zwracającą typ RECORD, ad-hoc utworzyć rekord z wartościami i zwrócić go.
Nie jest to trudne. Niestety "dokumentaliści" PostgreSQL zapomnieli pokazać jak taką funkcję wywołać.
Poniżej przykład znaleziony na jednym z forów Posrgresa:

CREATE FUNCTION xyz() RETURNS record AS
$$
declare
 abc RECORD;
begin
 abc := (1, 2);
 return abc;
end;
$$
language plpgsql;

select a, b from xyz() as (a int, b int);


Czyli wołamy SELECTA na tym, co zwróci nasza funkcja, ale jeszcze musimy zamapować wartości z rekordu na kolumny klauzulą AS.

29 paź 2009

Nie używać wielkich liter w nazwach funkcji/procedur składowanych PostgresQL!

Postanowiłem napisać swoją pierwszą procedurę składowaną w PostgreSQL.

Utworzyłem ją za pomocą kreatora w pgAdmin, ale za nic w świecie nie mogłem jej uruchomić.

Każda próba wywołania, kończyła się komunikatem:
No function matches the given name and argument types. You might need to add explicit type casts.

Sprawdziłem, że wywołuję ją w tym samym schemacie (public), w którym jest ona zdefiniowana.

Uprościłem treść procedury do minimum (usunąłem wszystkie parametry) i oto co mi zostało:

CREATE OR REPLACE FUNCTION "InsertEvent"()
  RETURNS integer AS
$BODY$
BEGIN

    RETURN 1;

END;$BODY$
  LANGUAGE 'plpgsql' VOLATILE
  COST 100;

A wołam ją tak:
select InsertEvent() AS ret

Nadal ten sam komunikat: nie może znaleźć wołanej procedury!

Minęła godzinka prób... ale zaraz, zaraz... nieco dziwnie to wygląda w definicji:
FUNCTION "InsertEvent"()

czasem pgAdmin wstawia mi cudzysłowia w identyfikatorach - nigdy nie zagłębiałem się dlaczego to robi. Zmieniłem wywołanie na:
select "InsertEvent"() AS ret

Hurrra! Działa!

Gdybym od początku nazwał procedurę nie używając konwencji CamelCase, np. insert_event, to cudzysłowia nie byłyby potrzebne i wszystko by się wykonało za pierwszym razem.

Szczegółowe wyjaśnienie co się stało:
Gdy użyłem nazwy procedury: InsertEvent, pgAdmin utworzył tzw. Quoted identifier, który jest case sensitive.
Dla nazwy: insert_event pgAdmin utworzył zwykły identyfikator, który jest case insensitive.

Więcej o identufikatorach w PostgreSQL tutaj


Wniosek:
Na wszelki wypadek lepiej używać standardowej konwencji nazywania identyfikatorów postgres: wszystkie litery małe rozdzielone znakiem "_". Np. insert_event

8 wrz 2009

Serwer ssl wykorzystujący boost::asio zawsze korzysta z jednego wątka

Napisałem serwer wykorzystujący boost::asio::ssl, który zawierał pulę 10 wątków obsługujących żądania klientów.
Szybko jednak okazało się, że żądania od klientów zawsze są obsługiwane w jednym wątku roboczym:
jeśli wątek ten wykonywał długą operację, to request od innego klienta czekał aż operacja w pierwszym wątku zakończy się.

Dokładną przyczynę tego błędu opisałem na grupie boost-users w poście:
"[asio] SSL enabled http server3 example is blocking"

Rozwiązanie problemu jest dość proste:
nie wykonywać długich operacji bezpośrednio w handlerze wywoływanym przez async_read(), async_write(). Zamiast tego handler powinien wysłać tę operację do io_serwis, aby wykonała się asynchoriniczie.

Przykład handlera wywyoływanego po asynchronicznym odczycie danych (requestu od klienta):

void Request::readHeaderHandler( const boost::system::error_code
&error, std::size_t bytesTransferred )
{
m_ioService.post( boost::bind
( &Request::executeCommandHandler::executeCommandHandler,
shared_from_this() ) );

}

void Request::executeCommandHandler()
{
// long running operation
char c;

}