我們的應用程序使用一些公平的網絡調用(它建立在第三方REST API之上),所以我們使用了很多異步操作來保持系統響應。 (使用Swirl保持理智,因爲應用程序是在tornado.gen
之前寫的)。所以,當需要做一些地理編碼時,我們認爲這將是微不足道的 - 拋出一些異步調用到另一個外部API,我們會很好。異步操作後神祕地掛起龍捲風 - 我該如何調試?
不知何故,我們的異步代碼神祕地懸掛着Tornado--該進程仍在運行,但它不會響應請求或向日志輸出任何內容。更糟的是,當我們將第三方服務器完全排除在外時,它仍然掛起 - 它似乎鎖定了異步請求返回後的任意時間段。
下面是一些存根代碼複製問題:
def async_geocode(lat, lon, callback, fields=('city', 'country')):
'''Translates lat and lon into human-readable geographic info'''
iol = IOLoop.instance()
iol.add_timeout(time.time() + 1, lambda: callback("(unknown)"))
而這裏的測試,通常(但並不總是 - 這是它是如何得到生產擺在首位),捕獲它:
class UtilTest(tornado.testing.AsyncTestCase):
def get_new_ioloop(self):
'''Ensure that any test code uses the right IOLoop, since the code
it tests will use the singleton.'''
return tornado.ioloop.IOLoop.instance()
def test_async_geocode(self):
# Yahoo gives (-122.419644, 37.777125) for SF, so we expect it to
# reverse geocode to SF too...
async_geocode(lat=37.777, lon=-122.419, callback=self.stop,
fields=('city', 'country'))
result = self.wait(timeout=4)
self.assertEquals(result, u"San Francisco, United States")
# Now test if it's hanging (or has hung) the IOLoop on finding London
async_geocode(lat=51.506, lon=-0.127, callback=self.stop,
fields=('city',))
result = self.wait(timeout=5)
self.assertEquals(result, u"London")
# Test it fails gracefully
async_geocode(lat=0.00, lon=0.00, callback=self.stop,
fields=('city',))
result = self.wait(timeout=6)
self.assertEquals(result, u"(unknown)")
def test_async_geocode2(self):
async_geocode(lat=37.777, lon=-122.419, callback=self.stop,
fields=('city', 'state', 'country'))
result = self.wait(timeout=7)
self.assertEquals(result, u"San Francisco, California, United States")
async_geocode(lat=51.506325, lon=-0.127144, callback=self.stop,
fields=('city', 'state', 'country'))
result = self.wait(timeout=8)
self.io_loop.add_timeout(time.time() + 8, lambda: self.stop(True))
still_running = self.wait(timeout=9)
self.assert_(still_running)
請注意,第一次測試幾乎總是通過,而這是第二次測試(以及致電async_geocode
)通常會失敗。
編輯添加:還請注意,我們有很多類似的異步調用到我們的其他第三方API工作非常好。
(爲了完整起見,這裏的全面實施async_geocode
及其輔助類(雖然上面的存根複製的問題)):
def async_geocode(lat, lon, callback, fields=('city', 'country')):
'''Use AsyncGeocoder to do the work.'''
geo = AsyncGeocoder(lat, lon, callback, fields)
geo.geocode()
class AsyncGeocoder(object):
'''
Reverse-geocode to as specific a level as possible
Calls Yahoo! PlaceFinder for reverse geocoding. Takes a lat, lon, and
callback function (to call with the result string when the request
completes), and optionally a sequence of fields to return, in decreasing
order of specificity (e.g. street, neighborhood, city, country)
NB: Does not do anything intelligent with the geocoded data -- just returns
the first result found.
'''
url = "http://where.yahooapis.com/geocode"
def __init__(self, lat, lon, callback, fields, ioloop=None):
self.lat, self.lon = lat, lon
self.callback = callback
self.fields = fields
self.io_loop = ioloop or IOLoop.instance()
self._client = AsyncHTTPClient(io_loop=self.io_loop)
def geocode(self):
params = urllib.urlencode({
'q': '{0}, {1}'.format(self.lat, self.lon),
'flags': 'J', 'gflags': 'R'
})
tgt_url = self.url + "?" + params
self._client.fetch(tgt_url, self.geocode_cb)
def geocode_cb(self, response):
geodata = json_decode(response.body)
try:
geodata = geodata['ResultSet']['Results'][0]
except IndexError:
# Response didn't contain anything
result_string = ""
else:
results = []
for f in self.fields:
val = geodata.get(f, None)
if val:
results.append(val)
result_string = ", ".join(results)
if result_string == '':
# This can happen if the response was empty _or_ if
# the requested fields weren't in it. Regardless,
# the user needs to see *something*
result_string = "(unknown)"
self.io_loop.add_callback(lambda: self.callback(result_string))
編輯:所以不少繁瑣的調試和記錄後在幾天內系統失效的情況下,事實證明,正如接受的答案所指出的那樣,我的測試由於不相關的原因而失敗。它也證明了它懸掛的原因與IOLoop沒有關係,而是一個有問題的協程立即掛着等待數據庫鎖定。
對不起,有針對性的問題,並感謝大家的耐心等待。
你是完全正確的,這是導致測試失敗。同樣正確的是,我原來的問題與它無關。 – necaris
可悲的是我不知道如何開始調試你的實際問題。 –