6
7
2011
0

GM 脚本:Ubuntu 中文论坛自动登录 & 展开代码

其实自动登录的部分很早就写了的,因为应用于同样的站点,所以就放一起了。

自动登录就不就了,据说是论坛一直就有的 bug。不过现在似乎好了,已经很少遇到需要重新登录的情况。代码展开嘛,是这样子的。Ubuntu 中文论坛支持使用[name]...[/name]的BBCode来调用 Geshi 插件进行语法高亮。不过高亮的代码却总是收缩起来,只能看到开头几行,要看完整的要么用滚动条,要么点击“展开”链接。Perl 的小骆驼书上有一句话我印象深刻:当程序超过一屏时,bugs 数量会突增。

// ==UserScript==
// @name           Ubuntu中文论坛自动登录
// @namespace      http://lilydjwg.is-programmer.com/
// @description    Ubuntu中文论坛自动登录
// @include        http://forum.ubuntu.com.cn/ucp.php?mode=login*
// @include        http://forum.ubuntu.com.cn/viewtopic.php*
// @include        http://forum.ubuntu.org.cn/ucp.php?mode=login*
// @include        http://forum.ubuntu.org.cn/viewtopic.php*
// ==/UserScript==

window.addEventListener("load", function(){
  setTimeout(function(){
    var links = document.querySelectorAll('a[onfocus]');
    if(links){
      var evt = document.createEvent("MouseEvents");
      evt.initMouseEvent("click", true, true, window,
			 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      for(var i=0, len=links.length; i<len; i++){
        if(links[i].innerHTML == '展开'){
          links[i].dispatchEvent(evt);
        }
      }
    }
    if(document.querySelector('input[name=autologin]')){
      document.querySelector('input[name=autologin]').checked = true;
      document.querySelector('input[name=login]').click();
    }
  }, 1000);
}, false);

点击这里安装。可以到这个页面测试。

Category: 火狐 | Tags: GreaseMonkey javascript 火狐
5
28
2011
5

MediaWiki迁移记

前天我正式切换到了 Arch 下,接下来是繁杂的配置迁移工作,以至于这篇日志现在才写。

虽然之前已经完成了大部分配置,但刚切换到 Arch 下不久,还是发现还有太多的东西要配置。于是像往常一样打开火狐,试图访问自己的维基,看到连接失败的错误页面,才想起我的 MediaWiki 还没迁移过来了。于是先把别的放在一边,装 httpd 和 mediawiki。然后准备迁移数据。这时我更郁闷了:我把相关资料记在了 wiki 里。。。。

于是开始 chroot,进到原先的 Ubuntu 系统中,Apache 启动了,然后是 MySQL。果然悲剧无处不在,因为没经历正常的启动过程,upstart 罢工了。折腾半天,终于手动把 mysqld 跑起来了。原来的 wiki 一切正常,很顺利地找到这个链接。我打算在新 wiki 里使用 SQLite 取代 MySQL,少一个进程,而且省得什么时候服务器跑不起来了。本来还打算使用 nginx 取代 Apache 的,但是在虚拟机里配置失败了。

首先是导出原 wiki 的数据。到 MediaWiki 安装目录下执行类似下边的命令:

php maintenance/dumpBackup.php --full --uploads > wiki-backup.xml
tar -czf wiki-images.tgz images

然后把新 wiki 配置好,再导入数据:

php maintenance/importDump.php wiki-backup.xml
 
mkdir temporary
cp wiki-images.tgz temporary
cd temporary/
tar -xzf wiki-images.tgz
cd ../
mkdir tempimages
cp temporary/images/*/*/* tempimages
php maintenance/importImages.php tempimages

导入页面数据时速度很慢,和导出时形成鲜明对比,不知道是什么原因。导入图片的过程很麻烦,但我不管了,反正有效。以上就是那篇文章上说的步骤。但是这个步骤是不完整的。尝试访问新 wiki,问题就出现了——

首先发现的是首页没改。这个在编辑历史里撒消下就可以了。

然后是统计数据被清零了。恢复后的首页赫然显示“目前已有0条目”。“最近更改”也没有了。在 maintenance 目录下可以找到很多脚本。我看名字选了几个运行:

php maintenance/initStats.php
php maintenance/initEditCount.php
php maintenance/rebuildrecentchanges.php

跑完之后那些统计数据就恢复了。

最后一个问题是:图片全部找不到了!我运行了几个维护脚本,都没效果。图片确实导入了,但是又找不到。无奈之下开始 Google。少顷,在 MediaWiki 官网上找到了答案

php maintenance/rebuildImages.php --missing

原来是要加个--missing参数。

至此,我的 wiki 终于恢复了。以前它一直正常时不觉得什么,这次没有在第一时间让其就绪,才终于知道,自己这两年的近 500 个页面的笔记实在是太有用了!

Category: Linux | Tags: mediawiki wiki
5
23
2011
51

MathJax:不错的数学公式显示引擎

我最初是在Mathematics - Stack Exchange这个网站上看到MathJax的使用的。当时我偶然发现StackOverflow底部还有一大堆 Stack Exchange 站点,就随便点了几个看看,然后就看到那些漂亮的数学公式,并发现它们竟然不是像维基百科那么用的图片!

不过因为我并不搞数学,一直没需求,所以一直也没尝试玩下 MathJax。前不久看到 Garfileo 的《基于 ASCIIMathML.js 的 is-programmer 博客数学公式书写及显示》一文,昨日又和 Fermat 聊起,才折腾了下。

先来测试几个公式:\(E=mc^2\),$$x_{1,2} = \frac{-b \pm \sqrt{b^2-4ac}}{2b}.$$

原来这个 \(\mathrm{e}^{- \mathrm{i} \pi} + 1 = 0\) 叫欧拉公式,今天才知道 -_-|||。

整个部署过程非常简单,加入以下脚本即可:

<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default"></script>

然后就是写 TeX 公式了,用\(\)括起来的是行内公式,用$$括起来的是自成一段的公式(术语叫什么我就不知道了)行间公式。

在公式上点右键还有些选项,包括选择使用 HTML+CSS 渲染还是 MathML 渲染、缩放、显示公式源码等。不过 MathML 的显示效果比较丑,而且诸如 Chrome 这些 Webkit 内核的浏览器还不支持。它会优先使用本地字体。我当然没有,于是它尝试使用网络字体。现代浏览器都会OK的,不过在火狐里,如果是本地文件(file://开头的),那么不能使用网络字体。这时候 MathJax 会使用图片代替。感觉挺智能的,而且还有各种模块和其的配置

MathJax 还支持在公式渲染之后再修改,我于是做了个简单的公式编辑器,可实时预览公式渲染的结果。有点郁闷的是,做这个网页时,相比公式处理部分,我大部分精力花在了排版上。。。另外,这个网页在 Chrome 下竟然出错了,清除缓存后才正常。以前 Chrome 也出现过这个问题,但火狐基本没有(我只在火狐3.6时代意外关机后遇到过)。


更新:

公式编辑器加了新功能,可通过 URL 的 hash 部分传递公式代码(尝试点击本段的链接),修改后 URL 也会变化,以便分享。不过发现了 Opera 浏览器的一个特性:会自动将手工输入的 URL 中的\替换成/,造成公式错误(比较点击和手工输入此链接地址的区别)。

Category: Javascript | Tags: javascript tex 数学
5
17
2011
16

强大的Vim插件——CountJump

今天,vim_dev邮件列表里有人说Vim要不要内建camelCase的移动方式。于是一帮人在那里讨论。我平时都是用fU找到下一个字母U的方式在很长的camelCase变量里移动,所以并不怎么关心讨论的结果,但想看看他们到底怎么想的。然后就看到 Ingo Karkat 说到了他的camelcasemotion插件和CountJump插件。Ingo Karkat 这人最近我经常通信的,他给我的colorizer插件(vim.orggithub)提了很多很好的意见(今天又来信了)。他还有个mark插件也很好用,同时高亮多个不同的单词/正则匹配的。

camelCase没太大的兴趣,所以我先没怎么看,看了CountJump的介绍,又见到这个在 patch 文件中跳转的插件,就下回来安装了,仔细读了帮助,才发觉,它可以实现我曾经希望有的功能。

下面开始正式介绍。首先,你得知道text-object是什么(不知道的童鞋请先:help text-object)。这个插件并不干实事,只提供了些函数,大体来说,完成了两类东西的自定义。其一是 patch 块这种区域,其二就是文本对象。定义的方式也很好,想简单,可以使用正则表达式;想更强大,支持使用自定义的函数。

我曾经折腾 Javascript 时非常希望有这样一个功能,可以像使用ci"来更改"string"中的内容这样快速更改 Javascript 正则表达式/regex/双斜线里的内容。可惜 Vim 没有内建这种文本对象。现在有了CountJump,自定义一个易如反掌:

call CountJump#TextObject#MakeWithCountSearch('', '/', 'ai', 'v', '\\\@<!/', '\\\@<!/')

就这样一句命令,然后就可以像使用i"a'这样使用i/a/来表示 Javascript 正则表达式双斜杠的文本内容了。

CountJump的区域移动也不错,不过除了那个用于 patch 的我还真没想到好的使用实例,只弄了这个:

1 call CountJump#Motion#MakeBracketMotion('', 'b', 'e', '\<\w\|\l\@<=\u\|_\@<=\w', '\w\>\|\l\u\@=\|_\w\@=', 1)
2 for mode in ['n', 'o', 'v']
3   exec mode . 'map b [b'
4   exec mode . 'map e ]b'
5 endfor

这个和camelcasemotion的功能类似,使用becamelCase或者under_score中的单词中移动,不过很不完美,实际使用时有一些问题。

最后,有一个非常令我兴奋的是,这样定义的文本对象也被surround.vim支持!在 Javascript 中想把一个字符串字面值改成一个正则表达式?cs"/即可!不过有点遗憾的是,自定义文本对象的名字不能是中文字符,自定义的中文标点文本对象surround.vim也不支持。

Category: Vim | Tags: vim
5
13
2011
6

Cairo 初体验

其实我觊觎 Cairo 很久了。昨天看到这篇文章,就下决心试了下。

简单图形的绘制

这个确实很简单,随便从后文的参考资料处抄点代码过来即可:

static gboolean on_expose_event(GtkWidget *widget,
    GdkEventExpose *event, gpointer data){
  cairo_t *cr;

  cr = gdk_cairo_create(widget->window);

  cairo_set_source_rgb(cr, 0, 0, 0);
  cairo_set_line_width(cr, 1);

  cairo_rectangle(cr, 20, 20, 120, 80);
  cairo_stroke_preserve(cr);
  cairo_set_source_rgb(cr, 1, 1, 1);
  cairo_fill(cr);

  cairo_destroy(cr);

  return FALSE;
}

看函数名基本上知道是在做什么了。我需要一个半透明的矩形,于是查文档,找到cairo_set_source_rgba函数即可。那个 cr,我是折腾到最后才明白它代表了绘制的 context (环境/上下文);至于那个cairo_stroke_preserve,我最终弄明白 Cairo 的绘制方法后才明白的。不管是cairo_rectangle还是我后来用到的cairo_arc,都是“画”了一个路径。之所以要把“画”加上引号,是因为路径本身是看不到的。使用cairo_stroke来描绘路径,使用cairo_fill来填充,而后面带_preserve的版本,是说路径处理完了还留着,下一个操作继续用。我之前犯了个错误,把所有的矩形和圆的路径都画好了才绘制,结果矩形和圆之间有道连线,圆的半径也被画出来了。折腾好久才知道应该在每个路径完成后就绘制。

事件处理

翻了好久的GTK、GDK文档,还是没弄明白该怎么在鼠标拖动时绘制。Google 一下,才看到 Garfileo 的这篇文章,在兴奋之余很有郁闷,Garfileo 以前的 Cairo 相关文章我都收集了,可他为什么后来又换博客了呢。。。。

  gtk_widget_add_events(window, GDK_KEY_PRESS_MASK |
      GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
  g_signal_connect(window, "button-press-event",
      G_CALLBACK(drawing_start), NULL);
  g_signal_connect(window, "button-release-event",
      G_CALLBACK(drawing_finish), NULL);
  g_signal_connect(GTK_OBJECT(window), "motion_notify_event",
      G_CALLBACK(drawing_move), NULL);

就这样就可以了。然后我就开始在每个motion_notify_event发生时画一遍,结果在画的时候图像不停地闪。问了下 gtalk 好友老猫,才知道原来只要在 expose 事件时画就好了,而图像改变后,调用下gtk_widget_queue_draw即可。至于为什么这样图像就不会闪,我至今还不清楚。

右键菜单

我需要在几种不同的选区之间切换。本来用 RadioButton 挺好的,但是我现在只会在 GtkWindow 或者 GtkDialog 里画图。后来就想到了右键菜单。查阅文档后就写出以下代码:

  menu = gtk_menu_new();
  menu_rect = gtk_menu_item_new_with_label("矩形");
  menu_circ = gtk_menu_item_new_with_label("圆形");
  menu_poly = gtk_menu_item_new_with_label("多边形");
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_rect);
  gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_circ);
  /* TODO 绘制多边形 */
  /* gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_poly); */
  g_signal_connect(menu_rect, "activate",
      G_CALLBACK(switch_to_rect), NULL);
  g_signal_connect(menu_circ, "activate",
      G_CALLBACK(switch_to_circ), NULL);
  g_signal_connect(menu_poly, "activate",
      G_CALLBACK(switch_to_poly), NULL);

  ...

  }else if(event->button == 3){
    gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
  }

但是这样点右键时只会显示一个小小的白色小边,就像没有菜单项在里面一样。百思不得其解,于是求助于 Google,最后不知道在哪里看到了,原来还需要对menu调用gtk_widget_show_all。想想也是,对窗口调用gtk_widget_show_all只是把窗口里的东西全部显示出来了,但我这个右键弹出菜单不是在窗口里的呀。

一点题外话

因为要绘制多个图形,本来准备自己写个单链表的,刚写完 node 结构体,突然想到,GLib 里不是有各种数据结构吗?于是再查文档,找到 GSList,愉快地用上了。g_slist_foreach这个函数非常好用。

参考资料

Category: 编程 | Tags: cairo C代码 gtk
5
9
2011
42

可笑的计算机网络应用设计实验作业

计算机网络应用设计实验是我们计算机网络课程的后继实验课程。今天是其上机的最后一天,于是得到最后要回去自己做的作业。看到其要求后我大跌眼镜——

要求完成FTP客户端、SMTP客户端、POP3客户端三个系统程序,以及一个应用程序

客户端操作系统为Windows XP。

FTP程序要求具有图形化界面、上传、下载功能,鼓励实现断点续传功能,SMTP/POP3程序具有邮件编写、发送、接收、阅读、删除等基本功能。FTP、SMTP、POP3程序使用socket方式编程,从创建socket、建立TCP连接开始,实现FTP、SMTP、POP3协议的功能,不得调用第三方控件(可使用操作系统自身的API函数)。编程工具可以使用C#或VC++。

应用程序要求实现一个网上书店的基本功能,要求具有前端(用户)和后端(管理)功能,基于Web运行方式。前端具有浏览书目、购物车等功能,后端具有管理书目、基本统计功能。编程工具可以是MS .NET(C#)或者J2EE 二选一,数据库可以是SQL Server、DB2、Oracle、Mysql、PostgreSQL之一。

我们的计算机网络课程是讲从物理层到应用层、从路由协议到TCP协议等等的,这个“计算机网络应用设计实验”总共有七次,要么使用思科的 Packet Tracer 模拟,要么在机柜的机器上弄,配置路由啊VLAN啊之类的,很是正常。可最后这个实验设计是怎么回事?就算是计算机网络应用程序设计课程也没有这么严格而变态的要求啊。这不仅仅是应用层的编程,而且还是Windows图形界面编程、数据库编程、C#或Java Web编程!这样的课程作业,实在应该在至少大二就布置下来,因为它可以作为另外至少四门课的大作业!!

对了,还有一点很令我郁闷——报告要求交Word 2003版格式的文件。根据我未读完的《Word排版艺术》里的信息,以及Word不断弹出的关于Normal.dot文件的对话框,我很确定Word会将文档的某些信息保存在本地的其它文件中,从而导致文档不具有可移植性,别人看到的排版可能根本不对。不过我估计那些用Word的老师没几个人听说过《Word排版艺术》。微软的罪孽在于,它把本来复杂的东西弄得看似简单,实则更加复杂。

武汉大学计算机学院,你太令我失望了!!!

参见:一名大学毕业生的反思已失效链接二

2011年10月10日再加个链接:珞珈山水BBS -- 作为一名毕业生,谈谈哪些行业或者企业不宜进入[原创]

Category: 未分类 | Tags: 教育
5
6
2011
9

login shell 和 non-login shell 不同造成的问题

上篇说到,我在Arch下的tmux的部分环境变量有问题。于是接下来我开始调查原因。最后终于真相大白。不过在揭露真相前,先详细说说问题是什么。

自从使用zsh以后,我在Ubuntu下发现我在~/.profile中设置PATH变量的代码在tty下没有起作用。但在~/.zshrc中设置又不行,因为图形界面登录时不会读取~/.zshrc。source 它也不行,因为可能导致双重设置(在一段时间里,我总是很奇怪地发现命令补全时某些命令会出现两次。。。)。于是,最后我的方案是这样的(箭头表示 source):

.profile --> .zsh/zshrc.env   <-+
.zshrc --> ZSHRC_ENV set? --No--+

这个方案在Ubuntu下一直工作良好。但在Arch+tmux下就出问题了。在tmux中的zsh启动前,ZSHRC_ENV已经设置,于是~/.zsh/zshrc.env没有被 source,于是$PATH设置得不对了。。。

在查阅tmux N次之后,我想,可能是某个启动文件覆盖了我自己的 PATH 变量的设置。于是打开 zsh 的文档,翻到这里:

5.1 Startup/Shutdown Files

Commands are first read from /etc/zsh/zshenv; this cannot be overridden. Subsequent be- haviour is modified by the RCS and GLOBAL_RCS options; the former affects all startup files, while the second only affects global startup files (those shown here with an path starting with a /). If one of the options is unset at any point, any subsequent startup file(s) of the corresponding type will not be read. It is also possible for a file in $ZDOTDIR to re-enable GLOBAL_RCS. Both RCS and GLOBAL_RCS are set by default.

Commands are then read from $ZDOTDIR/.zshenv. If the shell is a login shell, commands are read from /etc/zsh/zprofile and then $ZDOTDIR/.zprofile. Then, if the shell is interactive, commands are read from /etc/zsh/zshrc and then $ZDOTDIR/.zshrc. Finally, if the shell is a login shell, /etc/zsh/zlogin and $ZDOTDIR/.zlogin are read.

于是发现这一切的根源在于tmux里启动的是login shell!Arch的/etc/profile中重置了$PATH/etc/profile.d/locale.sh中重置了$LANG,所以造成我的tmux下的zsh环境变量不对的问题。于是我把设置移回了~/.profile中,然后将软链接~/.zprofile指向它。locale.shpacman不知道是什么包的,所以我就把它改成了:

[ -z "$LANG" ] && export LANG=en_US.UTF-8

至此,tmux部分的问题终于解决了!

Category: Linux | Tags: arch linux zsh tmux
5
5
2011
5

悲剧的 Arch

昨天尝试切换到Arch,结局十分悲剧。

配置文件复制完毕,启动Awesome正常,启动火狐正常,然后其它的就木有正常的了。。。

首先是tmux。Arch下的tmux一直有个让我十分郁闷的问题:PATH/LANG等环境变量会被重置,但又不是所有环境变量被重置,所以在.zshrc中的检测也失败了。结局就是,我使用脚本自动在tmux中启动的程序几乎全部失败。

其次是empathy、gnome-terminal等使用了GTK3,但是GTK3是不读取~/.gtkrc-2.0的。于是它们的外观很丑很丑。上网搜了下,~/.gtkrc-3.0的格式改变了。虽然变成类似CSS的更容易了,但我还是得花时候学习啊。另外,很多GTK3的窗口右下角都会有个调整块,看着十分碍眼。

wuala因为是Java写的,所以界面还好没变。不过,我忘记密码了!!!wuala的密码是本地存储的,没有办法找回。看了下自己设的密码提示,还是没猜出来。后来换回Ubuntu在登录wuala时看到了密码的长度,又想到了另外一个可能的密码。不过我不敢在这个已经登录的wuala里试了,万一错了就太悲剧了。

继续使用中发现更多的问题。点击empathy中的链接发现默认使用Chrome打开了,不知道该如何设置。fcitx输入法在empathy中工作不正常,一旦按回车或者退格之后就表现出来了,回车、退格无效、Ctrl-空格经常不起作用、某些字母穿透了输入法直接上屏。

我用的Ubuntu中文论坛的加速脚本是那个Python版的。我提取了其pyc文件(并且修改了端口号)。现在也悲剧了——Python 2.7 不认 Python 2.6 编译的字节码文件!于是我只好到官网下载了2.6版的,然后编译安装。然后又是找不到libpython2.6.so.0.1这个库文件,sudo ldconfig后依旧不行。过了好久我才意识到是配置文件里没有包含/usr/local/lib这个目录,在/etc/ld.so.conf.d/下建个文件里边写上后终于好了。

遇到的这么多问题就解决了Python2.6那一个。于是最后只好郁闷地回到Ubuntu下了。


2011年5月7日更新:tmux 那个诡异的问题解决了,见login shell 和 non-login shell 不同造成的问题

2011年5月31日更新:fcitx 那个问题已经在csslayer的指导下几天前就解决了,需要打补丁。Bug 报告链接,我修改的 PKGBUILD 文件

Category: Linux | Tags: arch gtk3
4
22
2011
5

初次使用 git 的“核弹级选项”:filter-branch

当初看 Pro Git 时就被作者这个“核弹级选项”的称呼吓到了,因此一直没敢好奇地去尝试。核弹啊,用对了威力无穷,用错了破坏力无穷!

但是,今天,我不得不用了,因为我想把我的 Python 脚本放到 github 上去公开。由于之前没想过要公开,所以不能肯定是不是把诸如密码之类的敏感数据直接写代码里了。于是我就用 git 的 pickaxe 选项-S来找找看。这个我也是今天才学到的哦,来源是stackoverflow上的若干问题,具体记不清了。

这个选项结合git log,其功能是把提交中包含某个文本的提交找出来。比如git log --stat -S密码就把所有提交中包含“密码”二字的提交找出来了,同时--stat告诉 git 我希望看到文件列表,以确定敏感数据在哪个文件里。另外说下zsh,我设置了hist_ignore_space选项,所以当查找密码时我在命令行最开始输入个空格,这样 zsh 就不会把这条命令写到命令历史里。

找了下,还真发现了两个包含密码的文件(其中之一 base64 过,另一个直接明文。。。)。我决定把它们转移到其它目录下,所以要删除这些文件。同时我意外地发现,有些提交中我引用了之前的提交,比如“(after xxx) fix xxx”这种。重写 git 历史后这些提交的 sha1 值会改变,所以这些提交信息也要重写。最开始我想把提交 sha1 修改为重写后的值,后来发现有点麻烦,得通过提交 sha1 查询一个备份中的提交信息(如果不事先保存相关信息的话),然后再通过提交信息查询重写后的提交的 sha1 值,还不知道 filter-branch 时能不能查询历史,于是作罢。还好这个仓库中只有一个这样的提交,所以我直接修改成~n表示引用第 n 次前的提交算了。最后,整个命令出来了:

git filter-branch --tree-filter 'rm -rf files_to_remove' --msg-filter '
sed s/d101601/~8/
' --prune-empty -f HEAD --all

命令挺复杂的,所以我是调用 Vim 写好的,然后在一个 clone 出来的仓库里先试运行。先解释下各个参数:

  • --tree-filter表示修改文件列表。
  • --msg-filter表示修改提交信息,原提交信息从标准输入读入,新提交信息输出到标准输出。
  • --prune-empty表示如果修改后的提交为空则扔掉不要。在一次试运行中我发现虽然文件被删除了,但是还剩下个空的提交,就查了下 man 文档,找到了这个选项。
  • -f是忽略备份。不加这个选项第二次运行这个命令时会出错,意思是 git 上次做了备份,现在再要运行的话得处理掉上次的备份。
  • --all是针对所有的分支。

试运行了几次,看到 150 多次提交逐一被重写,然后检查下,发现要删除的文件确实被删除了。于是高兴地到 github 建立新仓库,把脚本上传了。仓库名叫winterpy,因为网友 Vayn 建议我用 summerpy,而我更喜欢冬季 :-P

折腾完毕,我更加喜欢 git 了 :-)

Category: 版本控制 | Tags: python Git
4
19
2011
3

Vim的Python3有内存泄漏?继续修正!

给Vim的Python3支持打了个补丁,发到邮件列表上只有Bram表示希望有人来测试就没有下方了。于是,这么久了,这个补丁的内存泄漏问题一直未被发现,直到看到蓝色基因的这篇文章。花了一个下午,发现我原来的补丁不仅没有修正本来就有的内存泄漏,反而雪上加霜,浪费了更多的内存。现在终于弄好了,放在我的陈列室里了,同时还莫名其妙地修正了另一个小问题

既然是内存泄漏,我首先想到的是valgrind这个工具。于是跑了一下:

valgrind --leak-check=full --show-reachable=yes vim

在开启的 Vim 中我 source 了蓝色基因的测试脚本:

lcd %:h
tabedit tmpbuffer
setlocal buftype=nofile
 
python3 << EOF
for i in range(3):
    flines= ['x'*200] * 50000
    vim.command("%s+\\_.*++g")
    for fl in flines:
        vim.current.buffer.append(fl)
    del flines[:]
EOF

整个过程CPU占到100%,而且运行速度极慢,内存消耗也非常多。最后Vim终于按我的指令退出时,valgrind刷屏了大约十几秒钟!而其间我看到除了Python的字样外,还有不少rb的字样。难道Ruby支持也有类似的问题?不过我不管它。重新编译了个只有--enable-python3interp选项的 Vim,这回跑起来快了一些,也没有那么多不相干的内存泄漏了。我也学聪明了点,把信息重定向到文件:

valgrind  --leak-check=full --show-reachable=yes src/vim 2> log

这样可以方便地在log中找“if_py”字符串了。可惜我弄的时候没想到自己会来写博客,所以log文件并没有保存。。。

首先我找到了DoPy3Command这个函数,valgrind说它里面分配的内存没有被释放。这里边的PyUnicode_AsEncodedString这块是我加的:

    /* PyRun_SimpleString expects a UTF-8 string. Wrong encoding may cause
     * SyntaxError (unicode error). */
    cmdstr = PyUnicode_Decode(cmd, strlen(cmd), p_enc, NULL);
    PyRun_SimpleString(PyBytes_AsString(PyUnicode_AsEncodedString(cmdstr, "utf-8", NULL)));

然后我能怎么办呢?当然是查Python的文档了。于是注意到文档上说PyUnicode_AsEncodedString返回的是新的引用。又去看官方教程上的示例,才知道如果一个API返回了新的引用,那么用完后应当手动Py_XDECREF!就像是strdup函数,它内部帮你malloc了,你自己用完后要记着free掉。(Py_XDECREFPy_DECREF的差别是,前者可以传NULL。)

于是就改吧,所有通过PyUnicode_AsEncodedString得到的对象都要Py_XDECREF下。为此,不仅需要临时变量来存储这个对象,更让我郁闷的是,在两个Python版本共有的函数StringToLine中有这样一段代码:

    str = PyString_AsString(bytes);
    len = PyString_Size(bytes);

这里的两个函数/宏我之前是这样定义的:

#define _PyUnicode_AsBytes(obj) PyUnicode_AsEncodedString(obj, p_enc, NULL)
#define PyString_AsString(obj) PyBytes_AsString(_PyUnicode_AsBytes(obj))
#define PyString_Size(obj) PyBytes_GET_SIZE(_PyUnicode_AsBytes(obj))

这下我没辙了,只好又改了if_py_both.hif_python.c文件,加了两个宏:PyString_AsBytesPyString_FreeBytes。它们在 Python2 的代码中什么也不做,但是在 Python3 的代码中用来保存和释放中间对象:

#define PyString_AsBytes(obj) PyUnicode_AsEncodedString(obj, p_enc, NULL);
#define PyString_FreeBytes(obj) Py_XDECREF(bytes)
#define PyString_AsString(obj) PyBytes_AsString(obj)
#define PyString_Size(obj) PyBytes_GET_SIZE(bytes)

有人说,if it ain't broken, don't fix it。可是,虽然问题只出在 Python3 部分,我还是得改 Python2 部分,感觉很不爽。

这样改完,再次反复运行测试代码,结果不遂人愿,依旧泄漏了不少内存。于是继续valgrind,又找到这里:

    static void
BufferDestructor(PyObject *self)
{
    BufferObject *this = (BufferObject *)(self);

    if (this->buf && this->buf != INVALID_BUFFER_VALUE)
	this->buf->b_python3_ref = NULL;
}

然后再次查教程中的示例:

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

再看看 Python2 部分的代码,在相应的函数里有Py_DECREF,于是把这示例的最后一行给BufferDestructor以及WindowDestructorRangeDestructor加上。再测试,内存不再消耗100多M了,反复source也不会继续增加,于是作出结论:Vim 的 Python3 支持部分没有已知的 bug 了!

做完这一切,我只想说:Vim 这 Python3 支持也太 broken 了吧,中文经常乱码就算了,vim.error不能用我也忍了,竟然还内存泄漏!难道写这个代码的人也是初学Python C API啊?

不过抱怨归抱怨,还是很感谢原作者的,不然我连修正都不可能。不过,patch 弄好也提交了,却一直没人理我,原作者难道是一时兴起才写的、然后就消失了?

最后,补丁现在放到陈列室了。

Category: python | Tags: vim python C代码

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com