Inspiration from life

Software Engineer

Bo Cheng is a software engineer. He has been writing code since 2004, and is currently living in Chino Hills, CA. Below are some social links you can use to contact him.


虚拟机共享目录引起的权限问题

我在写代码的时候,不喜欢在mac上装相关依赖的组件,因为不同项目之间的依赖冲突有点麻烦,而且久而久之会污染我mac环境。所以我有一个VirtualBox,里面弄一个ubuntu的虚拟机,ssh进去安装,这个虚拟机可以随时抛弃,再重新建立。

但同时我又非常喜欢mac的GUI,所以我把开发目录在Host(mac)和Guest(ubuntu)之间建立共享(shared folder)。这样一来,我既可以拥有和生产环境一模一样的开发环境,也可以享受mac极好的GUI体验。

这样的开发方案一直很完美,直到有一天,发现了一个奇怪的问题……

我有一个docker的container运行在ubuntu里,这个container需要一个自定义的conf文件,由volume挂载进去。container启动后会自动往conf文件里添加一些内容。这个conf文件放在shared folder上,再volume进container。

当运行这个container的时候,我发现container在conf要写的内容死活写不进去。可是相同的代码,在mac上直接运行docker,则一切正常。

我花了好几个小时才找到原因:

  • ubuntu的conf文件的权限是770
  • 这个文件是root:vboxsf
  • container运行的帐号没权限写vboxsf的文件

那么解决方案就是,把文件改成yourname:docker

可是直接用chown,改不了,所以换另外一个办法,不用VirtualBox的自动挂载,自己用命令手动挂

首先找出你的帐号的uid

$ id -u $whoami
1000

再找docker的gid

$ cut -d: -f3 < <(getent group docker)
999

挂上去,这里dev是我的挂载名字

sudo mount -t vboxsf -o remount,gid=999,uid=1000,rw dev /media/sf_dev
999

这样重新启动ubuntu后,文件变成yourname:docker了,再运行container就没问题了。可是这种挂载方式重启ubuntu会丢,想要一直保持的话,用下面的方法

把下面的内容加到/etc/modules

vboxsf

把下面的内容加到/etc/fstab

dev /media/sf_dev vboxsf gid=999,uid=1000,rw 0 0

That is it.


多线程写代码的时候要注意安全

今天在做项目时候,发现获取当前登录user信息的rest service,偶尔会报错

An item with the same key has already been added.

只是偶尔报错,大部分时间是正常的,很奇怪。查了半天才查出原因,记录下来:

打开service代码,如下

public static class UserInfoProcess
{
  private static DataAccessDataContext db = new DataAccessDataContext();

  public static EmployeeInfo GetEmployeeInfoById(string employeeId)
  {
    return db.EmployeeInfos.SingleOrDefault(c => c.EmployeeId == employeeId);
  }
}

注意,这里的DataContext是static的,这里有个问题等会儿说。

查一下MSDN:

http://msdn.microsoft.com/en-us/library/system.data.linq.datacontext.aspx

Thread Safety


Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

MSDN说,DataContext只保证它static member的线程安全,而instance member则不保证。

那么什么叫线程安全呢,我觉得简单的说,就是多个线程同时访问一个object或者method的时候,内部的值要处理正确。

上面的service代码,调用了DataContextEmployeeInfos属性,这个属性是instance member,所以它的线程安全得不到保证。

线程不安全,是怎么体现的呢?我用reflector里反编译了EmployeeInfos属性,下面摘一点重要的出来

private ITable GetTable(MetaTable metaTable)
{
  ITable table;
  if (!this.tables.TryGetValue(metaTable, out table))
  {
    ValidateTable(metaTable);
    table = (ITable) Activator.CreateInstance(typeof(Table<>).MakeGenericType(new Type[] { metaTable.RowType.Type }), BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, new object[] { this, metaTable }, null);
    this.tables.Add(metaTable, table);
  }
  return table;
}

这个方法中,先看看tables这个dictionary里有没有对应的key,没有就反射create一个,再把它加到dictionary里。

如果只有一个线程,那么这里永远不会有问题,但是,rest service是一个多线程的环境。每一个request进来,都是一个线程,他们共享的是同一个DataContext(因为上面的service代码中,DataContext定义成static了),两个线程有一定的几率同时add一个key(这里没有lock,有lock,就可以线程安全了),导致开头说的那个错误。

所以,解决方法找到了:

  1. 把这个代码加上lock的机制,但是由于这是.Net Framework的代码,我们改不了,所以只能

  2. 这里DataContext不定义成static,换成instance的,每个线程访问自己独有的DataContext

PS:说句题外话,换成instance后,每个请求都要反射,可见Linq to sql性能不怎么样