Oct 8, 2011

Erlang i ** exception exit: {badfun,ok}

Poprawienie błędu ** exception exit: {badfun,ok} okazało się dość trudne (kilka minut na taką pierdołę?). Skoro już straciłem te kilka minut z życia na poprawkę to może chociaż ten post zaoszczędzi mi, a może nawet komuś jeszcze, trochę czasu w przyszłości. Jakie były objawy? Dodałem nowy gen_server do swojego serwera, oprogramowałem go i nawet działał :-) Pojawiał się jednak błąd ** exception exit: {badfun,ok} gdy odpalałem serwer ręcznie w Erlangowym shellu spod Emacsa. Oczywiście poza tym błędem dostawałem całkiem spory crash report z supervisora, ale to jest mniej istotne. Co było przyczyną? Po kilku minutach wpatrywania się w kod oraz komentowania jego fragmentów nie udało mi się nic znaleźć przyczyny. Skompilowałem plik poleceniem
 erlc -P plik.erl
i jasne było, że winę ponosi moje użycie makra debugHere jako
 ?debugHere()
zamiast
 ?debugHere
Jeżeli zamiast wywołania ?debugHere() wpiszemy w kod to, do czego się tłumaczy (co między innymi robi polecenie erlc -P) to zobaczymy coś takiego:
    begin
        .io:fwrite(user, <<"~s:~w: ~s\n">>, ["./plik.erl",68,"<-"]),
        ok
    end(),
Widać, że to makro wypisuje coś na ekran i zwraca atom 'ok'. Następnie ten atom jest wywoływany jako funkcja (para nawiasów za słowem kluczowym end). To oczywiście prowadzi do komunikatu ** exception exit: {badfun,ok} Nauczka
  1. Makro debugHere jako jedyne z rodziny debug* nie powinno być wywoływane jako funkcja. Ciągle o tym zapominam.
  2. Interfejs powinien być przede wszystkim spójny. Wszelkie udziwnienia prowadzą do błędów podczas używania takiego interfejsu.
  3. Makra potrafią być w Erlangu tak samo błędogenne jak w C.

Sep 3, 2011

Zaczynamy przygodę z NoSQL

NoSQL to świetna sprawa i naprawdę namawiam do eksperymentów z tą technologią. Wykorzystanie NoSQL upraszcza i przyspiesza rozwój małych małych aplikacji oraz bardzo upraszcza lub wręcz umożliwia development w przypadku tych większych. Jak zacząć zabawę z Riak? Jest to zaskakująco proste i szybkie. Polecam instalację ze źródeł - proces jest bezbolesny. Oto krótki filmik, który pokazuje krok po kroku jak to zrobić.

Setting up a Three Node Riak Cluster from Basho Technologies on Vimeo.

Dlaczego Riak? Bo jest prosty w obsłudze i szybki, a daje przy tym duże możliwości. Jest to baza danych oparta na Dynamo - krótko mówiąc, ta cecha sprawia, że zapomnisz co oznacza słowo downtime. Riak jest przy tym bardzo stabilny (w końcu to Erlang) i bezpieczny (wykorzystuje vector clocks oraz pozwala na rozwiązanie ewentualnych, konfliktujących zapisów). Dlaczego NoSQL? Poza prostotą (co dla mnie samo w sobie jest ogromną zaletą) dostajemy system, który jest odporny na awarie, nie posiada tzw. "single point of failure", łatwo się skaluje na wiele maszyn i jest przy tym bardzo szybki. Dlaczego nie SQL? Oczywiście relacyjne bazy danych mają swoje zastosowania i nikt im tego nie odbierze. Jednak z jakiegoś dziwnego powodu ludzie wykorzystują je nawet gdy nie ma takiej potrzeby. Niestety SQL to droga zabawka (nie wchodźmy teraz w szczegóły). Ale ja nie chcę tracić danych! I nie będziesz. Nie wiem skąd ten mit się wziął. Czy takie firmy jak Google (Big Table, Megastore) czy Amazon (Dynamo, SimpleDB) ryzykowałyby utratę danych swoich użytkowników? Są różne NoSQL. Do wyboru do koloru.
Po jednej stronie są super szybkie rozwiązania jak MongoDB. Osiągają ogromną wydajność dopuszczając przy tym możliwość utraty danych (w przypadku awarii maszyny). Pamiętajmy jednak, że zawsze mamy kilka replik danych i starty są możliwe gdy wszystkie repliki nagle się zepsują (kilka równoległych awarii w datacenter). Oczywiście mamy backup, więc jesteśmy bezpieczni.
Po drugiej stronie jest CouchDB. Baza, którą można wyłączać przez kill -9 i pod tym względem "rozwala" znane rozwiązania SQLowe. Prawdziwe niebezpieczeństwo czai się w SQLu, bo gdy zdecydujesz się na rozproszenie go na kilka maszyn to musisz implementować samemu wszystko to co w NoSQLach jest już zrobione i przetestowane.

Erlang i żądanie typu GET

Jak wiadomo protokół HTTP pozwala na różne typy żądań. RFC 2616 definiuje między innymi metodę POST. Gdy potrzebowałem wykonać zapytanie POST przy użyciu http_client w Erlangu udało mi się to zrobić dopiero po zapoznaniu się ze źródłami tej biblioteki. Poniżej krótki opis jak to zrobić.
http_post(Url, Data) ->
   inets:start(),
   inets:start(httpc, [{profile, ?MODULE}]),
   Result = httpc:request(post, {Url, [], "application/x-www-form-urlencoded", Data}, [], [], ?MODULE),
   inets:stop(),
   Result.
Jak widać jest trochę pracy :-)
Pierwsza linia mówi, że definiujemy funkcję o dwóch argumentach (Url oraz Data). W Erlangu, podobnie jak w Pythonie, nie podaje się typów argumentów.

Druga linijka uruchamia serwer inets. To wywołanie uruchamia kilka procesów, między innymi http client. Tworzy także domyślny profil dla httpc. Bez tego wywołania dostalibyśmy w trzeciej linijce błąd {error,inets_not_started}

Trzecia linijka tworzy profil httpc o nazwie ?MODULE (MODULE to makro, pod które jest podstawiana nazwa modułu, w którym jest użyte). W przypadku żądania POST nie można skorzystać z domyślnego profilu.

Kolejna linia wykonuje żądanie typu POST do Url i jako dane przesyła Data. Zwrócony wynik jest przypisywany do zmiennej Result.

Następnie wyłączamy serwer intets i w ostatniej linijce zwracamy wynik żądania.

Być może istnieje prostsza, jednolinijkowa metoda wykonywania żądania POST w Erlangu. Dla mnie działa opisany kawałek kodu i mam nadzieję, że jeszcze komuś się przyda. Jeżeli znasz prostszy sposób to będę wdzięczny za komentarz.

Jun 23, 2011

Zawieszający się appmon

Ostatnio zrobiłem równocześnie upgrade Erlanga do R14B03 oraz zainstalowałem nowe Ubuntu 11.04. Okazało się, że appmon przestał działać. Na początku myślałem, że błąd jest gdzieś w Erlangu - poprzednia wersja Erlanga działała na wszystkich maszynach bardzo dobrze (w tym na Ubuntu 11.04). Błąd objawiał się tym, że wywołanie funkcji appmon:start() nigdy się nie kończyło, a ekran monitora aplikacji się nie pojawiał. Wpadłem na pomysł, że powodem może być brak zainstalowanego TCL/TK. Po instalacji (w moim przypadku był to pakiet tk8.5-dev) wszystko działa. Może komuś jeszcze przyda się to rozwiązanie - a może nawet sam kiedyś je wygooglam ;-)

Jun 2, 2011

rebar 'generate not understood'

Jeżeli wykorzystujesz rebar do budowy projektu to możesz natknąć się na błąd o treści 'generate not understood' gdy chcesz zbudować release. Moim zdaniem błąd powinien brzmieć inaczej, bo wprawadza w błąd. Przecież rebar rozumie polecenie 'generate'. Przechodząc do rozwiązania - należy utworzyć poprawny katalog 'rel' i go skonfigurować.
$ mkdir rel && cd rel
$ ../rebar create-node nodeid=nazwa_mojej_aplikacji
$ cd ..
$ emacs rebar.config
wystarczy teraz dodać do pliku rebar.config linijkę
{sub_dirs, ["rel"]}.
I wszystko powinno działać - przynajmniej w moim przypadku to pomogło ;-)

May 23, 2011

Podświetlamy nadmiarowe znaki w komentarzu gita (GNU Emacs)

Przyjęło się, że pierwsza linia komentarza commita ma co najwyżej 50 znaków (w przypadku gita). Niektóre edytory (np. vim) podświetlają pierwsze 50 znaków aby pokazać programiście czy już przekroczył limit. Zobaczmy jak łatwo zrobić to w GNU Emacsie magit-mode Zakładam, że do obsługi gita wykorzystujesz magit-mode. Jest to świetny pakiet, który bardzo usprawnia pracę z tym systemem kontroli wersji. Łatwo zauważyć, że edycja komentarza odbywa się w magit-log-edit-mode. regex, który wyłapuje nadmiarowe znaki Naszym celem jest pokolorowanie znaków w kolumnach 51+ w pierwszej linii. Potrzebujemy regexa, który dopasuje się do tych znaków. Zaczynamy od symbolu
\\`
który oznacza początek bufora (pierwsza linia). Następnie musi wystąpić 50 znaków, które nie są nową linią
[^\n]\\{50\\}
Od tego momentu są znaki, które należy pokolorować. Łapiemy je wyrażeniem
\\(.*\\)
Ostatecznie nasze wyrażenie regularne wygląda tak
"\\`[^\n]\\{50\\}\\(.*\\)"
Wykorzystujemy funkcję font-lock-add-keywords aby ustawić font-lock-warning-face na znaki pasujące do wyrażenia
(font-lock-add-keywords 'magit-log-edit-mode
                        '(("\\`[^\n]\\{50\\}\\(.*\\)" 1 font-lock-warning-face)))
Zaraz po wyrażeniu widzimy liczbę 1. Oznacza ona, że interesuje nas pierwsza dopasowana grupa - to co jest między pierwszą parą nawiasów \\( oraz \\). Ustawiamy kolorowanie słów kluczowych przy starcie magit-log-edit-mode Aby przy starcie magit-log-edit-mode bufor został pokolorowany należy wywołać funkcję font-lock-fontify-buffer zaraz po starcie tego trybu. Możemy do tego wykorzystać funkcję add-hook
(add-hook 'magit-log-edit-mode-hook 'font-lock-fontify-buffer)
To rozwiązuje nasz problem. Podsumowanie Okazuje się, że w również w tym przypadku dodanie funkcji do Emacsa, o której nie pomyśleli autorzy jest bardzo proste. Ostateczny kod ma trzy linijki
(font-lock-add-keywords 'magit-log-edit-mode
                        '(("\\`[^\n]\\{50\\}\\(.*\\)" 1 font-lock-warning-face)))
(add-hook 'magit-log-edit-mode-hook 'font-lock-fontify-buffer)

May 14, 2011

Mit wydajność pre/post incrementacji w C++

Co chwilę napotykam na kogoś, kto twierdzi, że i++ jest mniej wydajne niż ++i. Większość osób przeczytało o tym na forum internetowym i nie potrafi wyjaśnić dlaczego tak jest. Co więcej - często trudno ich przekonać o nieprawdziwości takiego stwierdzenia. Przypomniałem sobie o tym gdy podobna dyskusja znalazła się na Warsztacie. Kiedy i dlaczego ++i jest wydajniejsze niż i++? Obie operacje wykonują się w dokładnie takim samym czasie (przy założeniu wykorzystania dobrego kompilatora) dla typów wbudowanych (np. int). Dla typów utworzonych przez użytkownika z przeciążonym operatorem ++() lub ++(int) (np. iteratory) operacja i++ działa wolniej. Różnica wynika z faktu, że i++ zwraca poprzednią wartość jaką miała zmienna. Musi zatem ją gdzieś zapamiętać - zachodzi dodatkowe kopiowanie, które w przypadku ++i jest zbędne. ++i może zwiększyć wartość i zwrócić już zwiększoną wartość. Dlaczego dla typów wbudowanych nie ma różnicy wydajnościowej? Zobaczmy jak wygląda assembler wygenerowany przez obie operacje (zakładając, że kompilujemy przy użyciu g++) Poniżej widać dwa programy w c++ różniące się tylko operacją ++x oraz x++
int main() {
    int x = 0;
    int y = ++x;
}

int main() {
    int x = 0;
    int y = x++;
}
Teraz generujemy kod asma (przełącznik -S w g++). Dla x++ kod wygląda następująco:
 movl $0, -4(%rbp)
 movl -4(%rbp), %eax
 movl %eax, -8(%rbp)
 incl -4(%rbp)
 movl $0, %eax
 leave
 ret
natomiast dla ++x
 movl $0, -4(%rbp)
 incl -4(%rbp)
 movl -4(%rbp), %eax
 movl %eax, -8(%rbp)
 movl $0, %eax
 leave
 ret
Wystarczy teraz policzyć ile operacji jest wykonywanych w każdym z przypadków aby zauważyć brak różnicy w wydajności.