2012年2月25日 星期六

libevent的loop怎麼停不下來?

(註一)

啊~libevent是什麼

有寫過網路程式的人應該對select()這個函式有印象。概念上可以把libevent想像成一個能把想要select的事件(ex. 某某socket能夠被讀或被寫),跟想要做的事情(ex. 在某某socket上交換資料),包成event-driven的跨平台函式庫。使用libevent,大部份的操作都是圍繞在struct event以及struct event_base這兩個結構上;其中一個是拿來存放socket、等待事件的flag、以及callback function的結構,另一個則是拿來存放event-loop的context的地方。

前陣子試著寫程式熟悉libevent的時候,發現在某些情況下在呼叫event_base_dispatch()之後,不管是用event_base_loopexit()或是event_base_loopbreak(),libevent的event-loop都沒辦法停下來。到底發生了什麼事呢?看一下event-loop的pseudo code(註二):
while (any events are registered with the loop) { /**< (1) */
    if (loop should be terminated because of event_base_loopexit())
       break; /**< (2) */
    if (loop should be terminated because of event_base_loopbreak())
       break; /**< (3) */
    if (EVLOOP_NONBLOCK was set, or any events are already active)
        If any registered events have triggered, mark them active.
    else
        /** blocked here!!! */
        Wait until at least one event has triggered, and mark it active. 
    for (p = 0; p < n_priorities; ++p {
       if (any event with priority of p is active) {
          Run all active events with priority of p.
          break; /* Do not run any events of a less important priority */
       }
    }
    if (EVLOOP_ONCE was set or EVLOOP_NONBLOCK was set)
       break; /**< (4) */
}
看起來如果要讓event-loop能順利離開迴圈,只要以下任一條件滿足就可以:
  1. event_base已經沒有任何需要等待的event。
  2. event_base_loopexit()被呼叫。
  3. event_base_loopbreak()被呼叫。
  4. 在event-loop被啓動的時候有加EVLOOP_ONCE或EVLOOP_NONBLOCK的flag(迴圈只會執行一次就自動離開)。
似乎一切都很合乎邏輯呀!怎麼會卡在"Wait until..."那裡呢?
好吧,我的寫法是在event-loop裡面只等socket的事件,沒有指定任何timeout;如果迴圈一直等在"Wait until..."那一行,但是卻沒有任何socket的事件發生,那會發生什麼事呢?沒錯...就是無窮無盡地等待、等待、再等待...

那解法為何呢?一個是放個等待timeout的事件在event-loop裡,再利用event_base_loopexit()或event_base_loopbreak()就可以順利跳出(只不過worst case還是要等個timeout發生才能跳出...);另一個是手動把所有加進event-loop裡的事件通通移掉,移乾淨之後就沒有東西可以讓event-loop等待,event-loop最後就會自己結束了。

咦?怎麼要把event-loop停下來這麼麻煩呢?心裡暗自抱了這個疑問很久,很來想通了一件事:如果不用libevent,硬是用select()來包自己的while-loop的話,至少也會需要一個timeout,讓自己的select()有機會可以跳出while-loop;如果是用libevent,絕大多數的寫法,自然就會加入這個timeout,所以其實也是一樣的道理而已。

結論就是,我之前遇到的情況其實大部份的人都碰不到;如果真的有人寫法也是像我一樣怪的話,那就自己認命在event-loop裡多加個timeout等一等,或是手動把所有加進event-loop的event通通移掉吧。

(註一) 圖片來源:http://monkey.org/~provos/libevent/libevent-benchmark.jpg
(註二) Pseudo code改自:http://www.wangafu.net/~nickm/libevent-book/Ref3_eventloop.html

2012年2月21日 星期二

一行CSS把IE8搞死

前一陣子寫一個網站,一發佈出去立刻有使用者哀號,每次一開就直接讓IE當掉強制關閉。這簡直是太令人莫名其妙了。我明明測試過了IE7/IE8/IE9啊。好吧,其實我是用IE9開到相容模式測試IE7/IE8。但是也沒辦法啊,M$FT不讓我同時安裝多個IE,甚至也不能降級啊。於是我就這麼踩到了這個莫名其妙的bug。 只出現在XP上的IE8的問題。而且只要你的CSS檔案中有指定body的background-image(如下),那麼一開啟頁面,IE8就開始隨意當機(Random Crash)。
body{
    background: url(../images/bg2.png) 50% 0 repeat-y;
    color:#7C7C7C;
    font: normal 12px "Helvetica Neue", Helvetica, Verdana, Arial,sans-serif !important;
    margin: 0px;
    padding: 0px;
}
一開始的時候以為一定是我哪邊JavaScript寫爛了,所以一直在刪除JS想要narrow down範圍,一直到我覺得我快把code砍光的時候,我才想到,該不會是jQuery爛了?然後我就去查一下我用的jQuery1.6.2跟IE crash有什麼關係。果然出來一堆。頓時覺得自己真是蠢啊,浪費了好多時間。那就更新到最新的jQuery吧。結果就好了。

這時候當然就想知道為什麼這麼神奇的CSS會導致IE GPF(General Protection Fault),啊,好久沒講GP了XD。好吧,結果就是沒人知道原因。jQuery的issue management也沒寫出root cause。如果有好心人知道原因記得告訴我一下。

2012年2月19日 星期日

Phone gap Facebook plugin一直要求重新登入,怎麼辦?

PhoneGap是一個讓你可以在行動裝置上能夠以HTML/CSS/JS開發的框架。支援了包括iOS,Android,Windows Mobile,甚至是BlackBerry等。但是如果你真的天真的相信它真的能夠達到跨平台的話,我大概只能祝你好運。首先Native Code的部分勢必是需要重寫的。再來每個平台的瀏覽器支援JS/CSS的程度也不同,所以光是跨iOS與Android就會有很大的問題了。更別說還要考慮Android上各種螢幕尺寸的問題。

我之所以用了PhoneGap的唯一原因是因為可以快速的實作出想要的App樣子。在有限的時間內要在單一平台上開發出一個完整App,這還是一個值得考慮的Solution。

但這裡沒有要寫教學文,網路上應該已經很多了,不過要小心的是大部分都是舊版的。PhoneGap從1.3之後有很大的改變,因此要特別小心你看的Tutorial文章用的是哪一個版本。

這裡其實是要抱怨Dave Johnson, Nitobi CTO釋放的Facebook Plugin有一個很基本但是卻很重大的問題。那就是登入過後的session cache處理有問題啊,這會導致每次app開起來都必須重新做一次facebook登入認證,然後導致很差的使用者經驗。

在罵之前還是要先說一下好話,Dave Johnson包裝的版本已經可以幫很多人省下很多的功了,只是還是不太應該埋下這個bug啊。

以下是pg-plugin-fb-connect.js中完整的source code:

PG = ( typeof PG == 'undefined' ? {} : PG );
PG.FB = {
  init: function(apiKey, fail) {
    // create the fb-root element if it doesn't exist
    if (!document.getElementById('fb-root')) {
      var elem = document.createElement('div');
      elem.id = 'fb-root';
      document.body.appendChild(elem);
    }
    PhoneGap.exec(function() {
      var session = JSON.parse(localStorage.getItem('pg_fb_session') || '{"expires":0}');
      if (session && session.expires > new Date().valueOf()) {
        FB.Auth.setSession(session, 'connected');
      }
      console.log('PhoneGap Facebook Connect plugin initialized successfully.');
    }, (fail?fail:null), 'com.phonegap.facebook.Connect', 'init', [apiKey]);
  },
  login: function(params, cb, fail) {
    params = params || { perms: '' };
    PhoneGap.exec(function(e) { // login
        localStorage.setItem('pg_fb_session', JSON.stringify(e.session));
        FB.Auth.setSession(e.session, 'connected');
        if (cb) cb(e);
    }, (fail?fail:null), 'com.phonegap.facebook.Connect', 'login', params.perms.split(',') );
  },
  logout: function(cb, fail) {
    PhoneGap.exec(function(e) {
      localStorage.removeItem('pg_fb_session');
      FB.Auth.setSession(null, 'notConnected');
      if (cb) cb(e);
    }, (fail?fail:null), 'com.phonegap.facebook.Connect', 'logout', []);
  },
  getLoginStatus: function(cb, fail) {
    PhoneGap.exec(function(e) {
      if (cb) cb(e);
    }, (fail?fail:null), 'com.phonegap.facebook.Connect', 'getLoginStatus', []);
  }
};


主要的問題就在標紅字的session.expires > new Date().valueof()那裡。
這邊的流程是每次App開起來會call init,然後如果call login去做Facebook OAuth,之後會把Facebook傳回的session存到瀏覽器的local storage。所以下一次App打開的時候在init的地方就會嘗試用之前存下的session去做初始化的動作。而問題就在這,每次在紅字這一行的condition check都是false啊。原因就在session.expires是日期字串,而new Date().valueof()是數值啊。剛剛又到GitHub上看了一下,還是沒人改掉。看來晚一點要來Push一個版本上去。

修掉這個問題的方法很簡單,只要用Date Object去parse session.expires轉成timestamp之後再做condition checking就可以了。

如下修正:
var eventStamp = Date.parse(session.expires);
if (eventStamp = new Date().valueOf())



--
Kenny Lee <kswlee@gmail.com>

iCoding計畫

這是一個共筆部落格計劃,目的是希望在各個不同公司/學校裡遇到的/解決的問題都可以被分享出來:)
希望是每天都可以有新文章,不過從2012/1/12發起之後一直到2/19才有第一篇文章。哈哈。有難度,但是希望慢慢的可以達到這目標。

目前已經募集到8個前同事/同學的加入。感謝大家的參與。

--
Kenny Lee
Related Posts Plugin for WordPress, Blogger...