mnesia粘滞锁的灾难

8 Nov

某天, 在erlang当中启动了大概几万个进程, 每个进程各自做类似于下列操作的时候

1
2
3
4
5
6
7
8
9
10
F = fun() ->
  case mnesia:read(record, Id) of
    [Record] ->
         NRecord = process_record(Record),
         mnesia:write(record, NRecord, sticky_write);
    [] ->
         mnesia:abort(no_rec)
  end
end,
mnesia:transaction(F).

mnesia卡死了, 调查了一下, 发现存在大量的全表读锁
看了一下mnesia源代码, 找出了祸首mnesia_locker.erl:

734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
do_sticky_lock(Tid, Store, {Tab, Key} = Oid, Lock) ->
    {WNodes, Majority} = w_nodes(Tab),
    sticky_check_majority(Lock, Tab, Majority, WNodes),
    ?MODULE ! {self(), {test_set_sticky, Tid, Oid, Lock}},
    N = node(),
    receive
	{?MODULE, N, granted} ->
	    ?ets_insert(Store, {{locks, Tab, Key}, write}),
	    [?ets_insert(Store, {nodes, Node}) || Node <- WNodes],
	    granted;
	{?MODULE, N, {granted, Val}} -> %% for rwlocks
	    case opt_lookup_in_client(Val, Oid, write) of
		C = #cyclic{} ->
		    exit({aborted, C});
		Val2 ->
		    ?ets_insert(Store, {{locks, Tab, Key}, write}),
		    [?ets_insert(Store, {nodes, Node}) || Node <- WNodes],
		    Val2
	    end;
	{?MODULE, N, {not_granted, Reason}} ->
	    exit({aborted, Reason});
	{?MODULE, N, not_stuck} ->
	    not_stuck(Tid, Store, Tab, Key, Oid, Lock, N),
	    dirty_sticky_lock(Tab, Key, [N], Lock);
	{mnesia_down, Node} ->
	    EMsg = {aborted, {node_not_running, Node}},
	    flush_remaining([N], Node, EMsg);
	{?MODULE, N, {stuck_elsewhere, _N2}} ->
	    stuck_elsewhere(Tid, Store, Tab, Key, Oid, Lock),
	    dirty_sticky_lock(Tab, Key, [N], Lock)
    end.
776
777
778
779
780
781
not_stuck(Tid, Store, Tab, _Key, Oid, _Lock, N) ->
    rlock(Tid, Store, {Tab, ?ALL}),   %% needed?
    wlock(Tid, Store, Oid),           %% perfect sync
    wlock(Tid, Store, {Tab, ?STICK}), %% max one sticker/table
    Ns = val({Tab, where_to_write}),
    rpc:abcast(Ns, ?MODULE, {stick, Oid, N}).

获取sticky_write锁的时候, 先向mnesia_locker进程发送一条test_set_sticky消息,然后等待返回
如果返回结果为granted, 说明粘滞锁已经存在, 继续后续操作
如果返回结果为not_stuck, 那么需要去获取粘滞锁, 这个过程是:
先全表读锁, 然后单记录写锁,然后表粘滞锁, 然后通知其他节点
如果这个过程是并发的,并且事先表上没有粘滞锁, 那么到生成粘滞锁之前的所有test_set_sticky操作都会返回not_stuck, 这就是大量全表读锁的来源

不过,777行这个needed?是怎么回事?
不清楚是不是需要就直接来一个全表大奖? 你当你是老虎机啊!

当然, 解决方法也很简单, 做并发操作之前让这个粘滞锁事先存在就可以了,

1
2
F = fun() -> mnesia:lock({table, record}, sticky_write) end,
mnesia:transaction(F).

mnesia版本4.4.19, erlang版本R14A

Tags: ,

Fiber in ruby

1 Nov

今天因为某个原因想了解一下fiber, 找到了这篇教程

http://www.infoq.com/news/2007/08/ruby-1-9-fibers

其中有一段示例代码

1
2
3
4
5
6
7
8
fib = Fiber.new do  
  x, y = 0, 1 
    loop do  
    Fiber.yield y 
    x,y = y,x+y 
  end 
end 
20.times { puts fib.resume }

看说明, 应该是:
fiber创建的时候,并不会立即执行
一直等到调用第一个fiber.resume的时候
会进入fiber的block,返回第一个Fiber.yield 的参数
再调一次resume, 再次返回下一个Fiber.yield的参数
一直到没有Fiber.yield 的时候, 返回fiber这个block的返回值
这种看起来奇怪的方式, 总感觉很眼熟的样子…

尝试翻译一下吧(resume不带参数的fiber):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
-module(test).
-export([main/0]).
 
resume(Fiber) ->
    Fiber ! self(),
    receive 
        Val ->
            Val
    end.
 
yield(From, Val) ->
    From ! Val,
    receive 
        NFrom ->
            NFrom
    end.
 
fiber(F) ->
    spawn(
      fun() ->
              receive
                  From ->
                      F(From)
              end
      end).
 
loop(From) ->
    loop(From, 0, 1).
 
loop(From, X, Y) ->
    NFrom = yield(From, Y),
    loop(NFrom, Y, X + Y).
 
main() ->
    Fiber = fiber(fun loop/1),
    lists:foreach(
      fun(_) ->
              Val = resume(Fiber),
              io:format("~p~n", [Val])
      end, lists:duplicate(20, 1)).

其实我写这篇blog只是想说:你妹!

Google Friend Connect Again!

21 Apr

我本以为已经解决掉那个可恶的Catchable fatal error问题了
但是据线人说, 这个错误仍旧存在, 但我却不能重现
看来rss错误和上文的异常其实不是一回事.

于是看代码(有整理):
wp-content/plugins/friendconnect-login/gfc_plugin.php

1
2
3
4
5
6
7
8
9
10
11
12
if ( isset( $_COOKIE[$gfc_site_cookie_name] ) ) {
    $gfc_site_cookie = $_COOKIE[$gfc_site_cookie_name];
    $gfc_request = 'http://www.google.com/friendconnect/api/people/@me/@self?fcauth=' . $gfc_site_cookie . '&fields=profileUrl';
    $gfc_curl = curl_init($gfc_request);
    curl_setopt($gfc_curl, CURLOPT_RETURNTRANSFER, true);
    $gfc_json_result = curl_exec($gfc_curl);
    curl_close($gfc_curl);
    $gfc_userdata = json_decode($gfc_json_result);
    $gfc_username = gfc_username_from_id($gfc_userdata->entry->id);
    $gfc_pwd = gfc_pwd_from_id($gfc_userdata->entry->id);
    $gfc_user_ID = gfc_login_user ($gfc_username, $gfc_pwd);
}

问题出在最后一行上面, gfc_login_user 调用了wp_login_user
而wp_login_user 是有检查gfc_username字段的, 如果该字段为空, 则返回WP_ERROR(正常情况下返回用户名)

如果google friend 返回的用户名为空(可能因为该用户很久未登陆而超时)
函数返回WP_ERROR, 并且在接下来的代码中被当作字符串使用,
终于在formatting.php line 2822 碰到一个不认账的, 挂了.

解决方案是:检查google friend 返回的gfc_username, 检查wp_login_user的返回值.

不过话说wordpress 把异常混在正常情况里面返回是怎么回事,
如果写插件的人不检查, 直接传递结果, 再弄个异常出来,
这下连异常从哪里来的都不知道了,
完全靠插件作者自觉么!!!!!

单引号惹的祸

17 Apr

今天晚上接到线人举报, 说我的blog rss显示不正常, 报错

Catchable fatal error: Object of class WP_Error could not be converted to string in /home/slepher/wordpress/wp-includes/formatting.php on line 2822

排查来排查去 把
< pre escaped="true" lang="php" >
< / pre >
里面的
git commit -m ‘add work1′
git commit -m ‘add work2′
统统改成了
git commit
可以正常显示了

先记下来, 具体原因再去排查吧.

使用 git-branch + git-rebase 释放你的洁癖

15 Apr

在使用git干活的时候, 经常会遇到下列状况:
做了一些改动, 提交, 在测试服务器checkout, 测试, 发现问题,
重新改动, 重新提交, 重新xx, 重新xx, 重新xx
….
在代码库中留下一堆commit

对于一个有洁癖的人来说, 这是一件非常可怕的事情

git-rebase 是一个很好的工具
可以修改之前的提交, 满足你的洁癖

当然这里不准备详细介绍
说明请看这里:
Git Interactive Rebasing
以及这里:
Git-rebase
或者你们家man page

但是, 且慢, 如果你rebase的是你已经提交到服务器的代码
那么当你重新pull的时候, 你会得到两份改动
一份是rebase 过的, 一份是没有rebase过的
/掀桌 我花这么大代价rebase 干啥

这个时候, 就需要找个办法把那些脏活藏起来, 我知道一个人, 干这种事情很拿手
当当当, 有请git-branch

我们假定你已经有了一个从远程git代码仓库
并且克隆到本地
并且你有对远程库做修改的权限

或者你有一个本地的库
然后新建了一个远程库
并且将本地代码推到远程库

反正你有了一个git版本库

如果你没有, 可以去github讨一份

下面我们开始

git push origin master:refs/heads/dirty_works  # 创建远程branch dirty_works
git checkout dirty_works  # 切换到dirty_works *

touch work1
touch work2
 
# 你做了两个提交, 并且把他们推到远程库里面去了
git add work1
git commit
git add work2
git commit
 
git push  # 这个时候已经将分支提交到远程

准备工作完成, 正式开始和谐一号任务:

git rebase master -i

这个时候, 你会看到编辑器自动被打开,
显示下面的一段东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pick 3b9eed9 add work1
pick 1d86afe add work2
 
# Rebase 2a81577..1d86afe onto 2a81577
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.

把第一行之后的pick全部改成fixup, 注释神马的就别管了

像这样:

1
2
pick 3b9eed9 add work1
fixup 1d86afe add work2

然后保存

rebase 工作完成
接下来保存工作成果:

git checkout master        # 回到主干
git merge dirty_works      # 合并所有dirty_works 的修改到master(当然是我们和谐过的!)
git push

查看和谐工作成果

$git diff master~ % 查看和上个版本的改动
1
2
3
4
5
6
diff --git a/work1 b/work1
new file mode 100644
index 0000000..e69de29
diff --git a/work2 b/work2
new file mode 100644
index 0000000..e69de29

接下来, 无论是要

毁灭证据:

git push origin :refs/heads/dirty_works  # 删除远程branch
git branch -d dirty_works                # 删除本地branch

或者是废物利用

git push origin master:refs/heads/dirty_works --force #把远程branch dirty_works重设为当前master

都是可以的

题外话
Q:
使用git-reset 也可以达成向master
用一个commit 提交dirty_works 中的修改的效果
为什么要动用git-rebase这么麻烦
A:
因为你在修改当前dirty_works的过程中
master 可能会因为别人代码的更新而更新
所以你需要git-rebase

* 本文形成的过程中, 作者使用的git版本号是 1.7.4.2,
可能因为1.7之前的git 没有那么聪明, 不能自动理顺远程branch 和本地branch之间的关系
你需要手动checkout

后记:
没文化,真可怕, 学习没到位啊…
本文前面说的都是废话, 你只需要一条命令: git merge [branch] –squash
作用是将目标branch和当前branch的差异作为一个commit 提交到当前branch

Tags:

一些数据以及计算

7 Mar

新华网北京1月15日电(记者王宇、姚均芳)中国人民银行15日发布的数据显示,2009年12月我国新增人民币贷款达3798亿元。至此,2009年全年人民币各项贷款增加9.59万亿元,同比多增4.69万亿元。

央行数据显示,2009年12月末,金融机构人民币各项贷款余额近40万亿元,同比增长31.74%,增幅比2008年末高13.01个百分点。2009年全年人民币各项贷款增加9.59万亿元,同比多增4.69万亿元。

分部门看,居民户贷款增加2.46万亿元,其中,短期贷款增加7570亿元,中长期贷款增加1.7万亿元;非金融性公司及其他部门贷款增加7.14万亿元,其中,短期贷款增加1.38万亿元,中长期贷款增加5万亿元,票据融资增加4563亿元。

截至2009年12月末,我国广义货币供应量(M2)同比增长27.68%。增幅比2008年末高9.86个百分点,比2009年11月末低2.06个百分点;狭义货币供应量(M1)余额为22万亿元,同比增长32.35%,增幅比2008年末高23.29个百分点,比2009年11月末低2.28个百分点;货币流通量(M0)余额为3.82万亿元,同比增长11.77%。全年累计净投放现金4027亿元,同比少投放71亿元。

存款方面,全年人民币各项存款增加13.13万亿元,同比多增5.44万亿元。从分部门情况看,居民户存款增加4.28万亿元,非金融性公司存款增加8.08万亿元,财政存款增加4368亿元。非金融性公司存款中,企业存款增加6.56万亿元。2009年12月份当月人民币各项存款增加5020亿元。
央行数据同时显示,2009年12月末,国家外汇储备余额为23992亿美元,同比增长23.28%。

================无聊的分割线=========================

如果央行没有故意做假账的话, 下列参数就是准的
存款和贷款都好统计, M0 则可以根据货币发行量和银行存量货币计算得出, 外汇储备余额也是可以统计的

稍微算了一下, 单位为万亿元
从08年底到09年底
贷款增加了9.59
存款增加了4.28 + 8.08 + 0.44 = 12.8 (剩下那部分是应该是金融系统间存款, 不算
金融系统持有的货币增加了 3.21 (其中2.04作为准备金上交央行
社会流通现金M0增加了0.4

理论上因为外汇储备多印的人民币大致在 2.3992 * 0.2328 * 6.8 = 3.8 左右 (09年的汇率一直稳定在6.8
考虑到美元计价的资产价格的变化, 这项其实不太准,不过相差也不会太大

09年整年没调整过存款准备金率, 为15.5%

结论:
央行除了强制结汇, 没印钱
对0.19万亿的货币退出了流通, 回收准备金2.04万亿
=======
Update1: 之前的准备金算错了… 基数应该是总的存款量, 而不是存贷差, 所以准备金是2.04而不是0.5
=======
Update2: 直接把存贷差再扣去保证金当作金融系统持有的货币是不对的, 金融系统会有买进资产, 卖出资产, 进行融资等行为, 所以上面的数字还会有误差, 不过预计不会太大.

Tags:

利率偏低状态下有期限借贷的租

15 Feb

假设通货膨胀率为0
假设市场的利率是A, 如果某人有权利以低于A的利率B从银行贷款Y
如果没有交易费用, 这个人就可以从银行以B的利率贷款, 并且以A的利率借出
每年的收益为Y(A-B), 如果换算成租那么
那么这个权利的租值是Y(A-B)/A

如果这个权利不是无限的, 而是有一定的期限, 那么, 这个租值必定会存在一个折扣

假设贷款年限为N年, 还款方式为按年等本还款那么
第一年年底的收益为Y(A-B)
第二年的收益为Y(A-B)* (N-1)/N
第x年的收益为Y(A-B) * (N-x+1)/N
第N年的收益为Y(A-B) * 1/N

把这N年的收益进行贴现计算, 利率A的贴现率是1/(1+A)记为q

就为 Y(A-B)(q + (N-1)/Nq^2 + (N-2)/Nq^3 + … + 1/Nq^(N -1)) 记为 K

根据计算
K = Y(A-B)/A(1-(1-1/(1+A)^N)/(N*A))
相对于无期限贷Y(A-B)/A 的折扣是1-(1-1/(1+A)^N)/(N*A)

这个值
在N为20, A为10%的时候, 为0.926
在N为20, A为7.5%的时候, 为0.509
在N为20, A为5%的时候, 为0.377

Continue reading 

Tags:

从Application Settings 到IOS4.x 多进程

10 Dec

话说前段时间调IOS程序的时候, 出了一个诡异的问题

在application settings中设置某选项为A
在应用程序中读取application settings, 修改设置为B,
使用UserDefaults 保存之后, 退出程序,
进入application setttings, 发现相应的设置没有修改, 还是A
同样, 修改application settings为C之后,
开启应用程序, 发现读取的setting 没有变化, 仍旧是B

简单地说, 就是

在application settings徘徊于A和C之间的时候
应用程序中的设置一如既往地在装B
这不是坑爹嘛…. 这么简单的过程, 没理由会出错啊

Continue reading 

Tags:

长度是怎样炼成的

8 Dec

在网上看到了这一系列文章, 可惜由于google reader 未能收录, 不能在gr里面共享, 就列在这里吧.

点没有长度和面积,为什么由点组成的线和面会具有长度和面积?
“长度”“面积”这些词汇究竟是在怎样的意义上被使用的?
有的时候我们把点的长度叫做零,有的时候叫做无穷小,这两个称呼是不是都有道理?
无穷个零相加是不是还得零?(其实和第一个问题是一个意思,无穷个点怎么加成线段的?)

长度是怎样炼成的(序)
长度是怎样炼成的(1)
长度是怎样炼成的(2)
长度是怎样炼成的(3)
长度是怎样炼成的(4)

Google Friend Connect ! (2)

20 Nov

接前话,  Friend Connect Login 的avatar一直不能正常显示

avatar of Friend Connect Login could not show correctly 0n wp-3.0.1

估计是兼容性的问题, 于是找到源代码

so i find the source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function gfc_wp_get_avatar($avatar, $id_or_email, $size, $default, $alt) {
        global $wpdb;
        if (!empty($id_or_email->user_id)) {
                $email = $id_or_email->comment_author_email;
                $query = "SELECT * FROM `wp_users` WHERE user_email = '$email' LIMIT 1;";
                $res = $wpdb->get_col($query);
 
                // We dont know if this user, so return whatever was given to me
                if (count($res) <= 0)
                        return $avatar;
                // Do not change the admin's image
                if ($res[0] == 1)
                        return $avatar;
 
                // Get the image and return the altered $avatar
                $image_url = get_usermeta( $res[0], "image_url");
                        return "<img class="avatar avatar-{$size} photo avatar-default" src="{$image_url}" alt="" width="{$size}" height="{$size}" />";
        } else {
                return $avatar;
        }
}
上面的代码有两处问题, 一个是wordpress的表名字现在是动态生成的, wp_users 需要用$wpdb -> users 代替
另外一处是接口变动,  $id_or_email 目前就代表email, 没有更多的衍生含义
于是上面的代码改为
there is two problems
1. the database table name is generated dymatic now,  we should use $wpdb-> users  as the users table name ( take place of wp_users)
2. $id_or_email is just email (I dont know why it changes)
so I change the code to:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function gfc_wp_get_avatar($avatar, $id_or_email, $size, $default, $alt) {
        global $wpdb;
        if (!empty($id_or_email)) {
                $email = $id_or_email;
                $query = "SELECT * FROM $wpdb->users WHERE user_email = '$email' LIMIT 1;";
                $res = $wpdb->get_col($query);
 
                // We dont know if this user, so return whatever was given to me
                if (count($res) <= 0)
                        return $avatar;
                // Do not change the admin's image
                if ($res[0] == 1)
                        return $avatar;
 
                // Get the image and return the altered $avatar
                $image_url = get_usermeta( $res[0], "image_url");
                        return "<img class="avatar avatar-{$size} photo avatar-default" src="{$image_url}" alt="" width="{$size}" height="{$size}" />";
        } else {
                return $avatar;
        }
}
1
 

wordpress 3.0.1 测试通过

wp-3.0.1 test passed

Tags: