找到归档 2014-01 的文章 8 篇.


GDB调试Go程序

Posted on 2014-01-28 14:45:24 golang

摘要:

说明:作为一门静态语言,似乎支持调试是必须的,而且,Go初学者喜欢问的问题也是:大家都用什么IDE?怎么调试?

其实,Go是为多核和并发而生,真正的项目,你用单步调试,原本没问题的,可能会调出有问题。更好的调试方式是跟PHP这种语言一样,用打印的方式(日志或print)。

阅读全文

一种新的python局部调试手法

Posted on 2014-01-28 12:47:57 python

我们都知道,python里面可以用pdb来调试代码。但是pdb往往不大好用。有时候调试代码往往在多重条件里面,直接用pdb需要下条件断点,设定复杂的条件。

一个简单的办法就是这么干。

__import__('pdb').set_trace()

但是有的时候,连这个出现的条件都不满足。例如,代码必须在一个受限环境中运行,很难拿到console,或者其他林林总总的毛病。这时候,我们还有一招秘技。

import pdb, socket
s = socket.socket()
s.connect(('127.0.0.1', 8888))
f = s.makefile()
pdb.Pdb(stdin=f, stdout=f).set_trace()

在连接到的目标端口上,提前用nc做好监听,就可以在触发断点的时候直接连接上来调试。


Automatically start a local godoc web server

Posted on 2014-01-23 22:40:55 golang

Godoc is awesome, It allows you to browse all the documentation on your local machine by running the following command:

godoc -http=:8090

Once you do this you can happily browse through all the documentation offline. However, you can start this process as an upstart daemon and let it run in the background. All you have to do is create a file called /etc/init/godoc.conf with the following content:

#Upstart script to start godoc server
#*This obviously works on machines with upstart*
start on runlevel [2345]
stop on runlevel [06]

script
  export HOME=/home/roy/coding
  export GOROOT=$HOME/go
  export GOBIN=$GOROOT/bin
  export GOPATH=$HOME/gocode
  $GOBIN/godoc -http=:8090 -index=true
end script

You just need to make sure that the env vars are adjusted to your setup. Also, using -index=true runs a full text search on all your documentation (which is simply awesome). Now, you will have your godoc server at http://localhost:8090/ all the time.

On a related note, godoc can be used from the command line like:

#godoc <pkg name> Type/Func
godoc net Listener

linux下的自动编译工具

Posted on 2014-01-23 13:43:54 os

在Linux下面,编写makefile是一件辛苦的事情。因此,为了减轻程序员编写makefile的负担,人们发明了autoconf和automake这两个工具,可以很好地帮我们解决这个问题。 我们可以通过一个简单的示例来说明如何使用配置工具。

1. 首先,编写源文件hello.c。
#include <stdio.h>

int main(int argc, char** argv[])
{
    printf("hello, world!\n");
    return 1;
}
2. 接下来,我们需要创建一个Makefile.am,同时编写上脚本。
SUBDIRS=

bin_PROGRAMS=hello
hello_SOURCES=hello.c  
3. 直接输入autoscan,生成文件configure.scan,再改名为configure.in。

修改脚本AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)

为AC_INIT(hello, 1.0, roy@rootk.com)

同时,在AC_CONFIG_HEADER([config.h])后面添加

AM_INIT_AUTOMAKE(hello, 0.1)

4. 依次输入aclocal命令、autoheader命令
5. 创建4个文件,分别为README、NEWS、AUTHORS和ChangeLog
6. 依次输入automake -a、autoconf命令
7. 输入./configure,生成最终的Makefile
8. 如果需要编译,输入make;如果需要安装, 输入make install;如果需要发布软件包,输入make dist

关于Gevent的那些事儿

Posted on 2014-01-22 12:35:42 gevent

1. spawn一个greenlet之后,不会立即运行。

除非有隐式或者显示的手段switch到这个greenlet。

2. gevent.spawn做了什么?

只是创建了一个greenlet实例,并加入到了greenlet链,还没有运行它。

3. greenlet.switch到底做了什么?

greenlet.switch最终会调用greenlet.c中的C函数。 greenlet.c中g_swith会扫描greenlet链,如果发现没有启动的greenlet,则调用g_initialstub启动它。 实际上g_initialstub会在greenlet类实例中查找run属性,找到了则运行它。 如果greenlet是已经启动的,则switch到main hub.

4. g=gevent.spawn(func); g.run()做了什么?

按照3的说法,它首先创建了greenlet实例,设置了实例的’run’属性。 然后直接运行刚才指定的’run’属性,也可以叫它callback,是一个callable对象。

5. 当创建多个greenlet实例,然后并不run他们。

最后再启动一个greenlet,为什么之前创建的greenlet还是可以运行?

因为最后运行的那个greenlet中,发生了switch,对应到greenlet.c中的g_swith。 它会扫描greenlet链,如果发现没有启动的greenlet则启动它们。 如果最后运行的那个greenlet中没有发生switch,则在它之前创建的greenlet无法运行。

例如下面的代码:

def g1():
    print 'g1'
    gevent.sleep(2)

def g2():
    print 'g2'
    gevent.sleep(2)

def g3():
    print 'g3'                                                                                                                    
    gevent.sleep(1)

gevent.spawn(g1)
gevent.spawn(g2)
gevent.spawn(g3).run()

输出:

g3
g1
g2

如果函数g3,没有最后一行gevent.sleep(1), 则g1和g2都不会执行 只是输出’g3’字符串之后,进程退出。

6. hub.Waiter类有什么作用?

Waiter用来在greenlet之间通信,它简单包装了greenlet.swith。 典型的工作过程如下:

>>> result = Waiter() #1
>>> timer = get_hub().loop.timer(0.1) #2
>>> timer.start(result.switch, 'hello from Waiter') #3
>>> result.get() #4
'hello from Waiter'
  • #1: 创建一个Waiter类实例,在init中什么也不做。

  • #2: 启动一个libev的watcher(libev有许多watcher例如io/timer/signal等,可以理解为对特定对象的#监视对象#)

  • #3: 为这个#监视对象#设置callback和参数,这里的是Waiter实例的switch函数,参数为字符串。 接着立即让libev开始监视这个对象。 timer.start只是让libev启动一个time类型的watcher,并在事件发生后,执行回调。 waiter.switch的代码只有关键的一行:greenlet.switch(value),它切换当前协程到main hub. 但是watcher并没有执行result.switch,只是将它加入了libev的callback列表。 要等待libev.loop.run启动主循环。 记住,这里给libev的watcher传递了一个callback,并带有参数。

  • #4: 这里很关键, waiter.get中有self.hub.switch(),表示立即出让时间片到main hub. 流程到了hub.switch这里,它调用greenlet.switch(self)。 这里的greenlet是C代码中的greenlet类实例,是所有greenlet的父类。 所以我们到了greenlet.so模块内部。

    注意这时,虽然main hub已经被创建了,但是从来没有运行过。 所以greenlet.c中switch会调用g_initialstub执行main hub的run函数,这里会启动core.so->loop.run,启动libev的 ioloop.

    因为#3给libev的callback中增加了watcher(waiter.switch),所以ioloop启动后会检测watcher事件,事件发生后调用watcher的callback.

    就会调用waiter.switch,其中一行代码:self.greenlet.switch(value) 因为我们一直在main hub中,所以又回到了hub.switch,在hub.switch中是return greenlet.switch(self),所以这里会返回。 流程最后又回到了wait.get中,其中returen hub.switch()返回最终的值。

7. gevent.sleep到底做了什么?

结合Waiter是如何工作的,再看sleep的代码:

def sleep(seconds=0, ref=True):
    # 得到main hub
    hub = get_hub()

    # 得到hub.loop,这是libev的主循环
    loop = hub.loop

    # 如果sleep时间小于等于0
    # 启动一个waiter,直接调用loop的run_callback运行waiter.switch
    # 再调用waiter.get()会立即返回
    # 因为我们没有给core.so->loop传递任何watcher,所以会立即返回
    if seconds <= 0:
        waiter = Waiter()
        loop.run_callback(waiter.switch)
        waiter.get()
    else:
        # 这里启动了一个libev的timer类型的watcher
        # 然后传递给hub.wait
        # hub.wait的代码,是Waiter类的典型用法:
        # waiter = Waiter()
        # unique = object()
        # watcher.start(waiter.switch, unique)
        # try:
        #      result = waiter.get()
        #      assert result is unique, 'Invalid switch'
        # finally:
        #      watcher.stop()
        # 启动一个timer类型的watcher,
        # 在waiter.get中切换到main hub,然后在事件发生后调用waiter.switch
        # 在waiter.switch中,执行self.greenlet.switch,控制流又会回到waiter.get中
        # 因为self.greenlet是调用waiter.get的协程,也就是调用gevent.sleep的协程
        hub.wait(loop.timer(seconds, ref=ref))
8. monkey.patch_xxx很邪恶
  • 首先monkey.patch_xxx只需要执行一次即可,即在main中执行.

  • 其次monkey.patch_all会给所有模块做patch. 解决办法是,只使用特定模块的patch,如monkey.patch_socket

  • 最后monkey.patch_xxx会给所有导入的模块做patch 解决办法是在其他需要导入的模块执行下面的操作:

    import a
    import socket
    reload(socket)
    print socket.socket     # <class 'socket._socketobject'> 
    

Tornado的Connection reset by peer - 记一次tornado的BUG修复

Posted on 2013-01-07 17:29:45 tornado

现场抓到的错误:

[W 130107 15:59:42 iostream:425] Read error on 8: [Errno 104] Connection reset by peer
[W 130107 15:59:42 iostream:359] error on read
    Traceback (most recent call last):
      File "/usr/lib/python2.7/dist-packages/tornado/iostream.py", line 354, in _handle_read
        if self._read_to_buffer() == 0:
      File "/usr/lib/python2.7/dist-packages/tornado/iostream.py", line 421, in _read_to_buffer
        chunk = self._read_from_socket()
      File "/usr/lib/python2.7/dist-packages/tornado/iostream.py", line 402, in _read_from_socket
        chunk = self.socket.recv(self.read_chunk_size)
    error: [Errno 104] Connection reset by peer

这要从TCP的结束方式谈起:

大家都知道TCP的正常结束方式是四路握手,过程是这样:

  1. (B) –> ACK/FIN –> (A)

  2. (B) <– ACK <– (A)

  3. (B) <– ACK/FIN <– (A)

  4. (B) –> ACK –> (A)

但是四路握手不是关闭TCP连接的唯一方法,有时,如果主机需要尽快关闭连接(或连接超时,端口或主机不可达),RST (Reset)包将被发送.

注意,由于RST包不是TCP连接中的必须部分, 可以只发送RST包(即不带ACK标记). 但在正常的TCP连接中RST包可以带ACK确认标记。

所以IE浏览器在关闭一个连接时,它先会发送一个RST包给tornado,但是tornado没有正确处理。导致了recv函数返回errno.ECONNREST错误。

但是chrome、firefox是正常的关闭连接,执行TCP四路握手,所以tornado不会报错。

出现问题的代码在iostream.py的421行,代码片段:

try:
    chunk = self._read_from_socket()
except socket.error, e:
     # ssl.SSLError is a subclass of socket.error
     logging.warning("Read error on %d: %s", self.socket.fileno(), e)
     self.close()
     raise

self._read_from_socket()函数,它直接从socket接收数据。但是没有处理ECONNREST,导致错误发生。

但是这个错误只是在ubuntu 12.10上面发生,它使用的tornado版本是2.3。

gitbub上最新的版本已经修复了这个错误,2012年11月3号,有commit为证:

commit 3258726fea5bcd1b401907653bc953ce63d5aeb2 Author: Ben Darnell ben@bendarnell.com Date: Wed Oct 3 22:36:46 2012 -0700

Reduce log spam from closed client connections.

Added a bunch of tests for keepalive functionality and fixed two cases where we’d log an exception when the client was gone. ECONNRESET errors in IOStream reads now just close the connection instead of logging an error (the exception information is still available on stream.error in the close callback for apps that want it). HTTPConnection now also checks for a closed connection and cleans up instead of logging an error.

IOStream now raises a new exception class StreamClosedError instead of IOError.

最新修复的代码是这样:

try:
    chunk = self.read_from_fd()
except (socket.error, IOError, OSError), e:
    # ssl.SSLError is a subclass of socket.error
    if e.args[0] == errno.ECONNRESET:
        # Treat ECONNRESET as a connection close rather than
        # an error to minimize log spam  (the exception will
        # be available on self.error for apps that care).
        self.close(exc_info=True)
        return
    self.close(exc_info=True)
    raise

并且增加了错误处理,不再直接报IOError,而是自定义错误StreamClosedError错误。


pdb常用调试命令

Posted on 2012-11-30 18:30:54 python

摘要:

h(elp),会打印当前版本Pdb可用的命令,如果要查询某个命令,可以输入 h [command],例如:“h l” — 查看list命令 l(ist),可以列出当前将要运行的代码块

阅读全文

关于python装饰器的总结

Posted on 2012-05-08 12:23:39 python

# 因为wrap函数的参数只能是一个函数
# 而wrapper的参数又只能是传递给函数的参数列表
# 如果想要再次处理被装饰函数的返回结果,只能在最外层的函数参数中指定
# 这里就是在deco函数的参数中
# 如果只用两层嵌套就无法做到
def deco(render=None):
    def wrap(func):
        def wrapper(*args,**kwargs):
            result = func(*args,**kwargs) ###
            return render(result)  ###
        return wrapper
    return wrap


my_render = lambda x: str(x) + ' --my_render'

@deco(render=my_render)
def test():
    return "this is test!"

print test()

这里正是因为想让result被再次处理,所以要在最外层函数的参数中制定调用的处理函数,就是my_render。