ld: library not found for -lgcc_s.10.5 在 Mac 下 NPM 编译安装的常见错误

情况:通过 Node 的 NPM 编译安装某些模块的时候报错:

ld: library not found for -lgcc_s.10.5  
clang: error: linker command failed with exit code 1 (use -v to see invocation)  

解决方案:
到 AppStore 中安装 XCode 7。装完以后,打开 XCode 并且接受 license 协议。更新之后,node-gyp 编译就没有这个问题了。

Advertisements

mac 网络模拟工具

先说需求,为了测试手机应用在各种网络下的情况,所以准备找一个模拟各种网络情况的工具。

其他平台的模拟工具: Linux 平台:Facebook 的开源移动网络测试工具 Augmented Traffic Control(ATC)
Windows 平台:微软 visual studio 自带的网络模拟工具 Network Emulator for Windows Toolkit (NEWT)

因为工作环境的问题,这里讨论 mac 下 xcode 自带的 Network Link Conditioner(NLC)

安装

NLC 是 xcode 的插件,可以在 “Hardware IO Tools for Xcode” 中找到。你可以在苹果开发者页面找到这个插件,传送门 https://developer.apple.com/downloads/?q=Hardware IO Tools

Download

查找 “Network Link Conditioner”, 然后选择 “Hardware IO Tools for Xcode” package.

Package

下载好之后, 打开 DMG 然后双击 “Network Link Condition.prefPane” 来安装.

System Preferences

装好以后, 你就可以通过 ON 和 OFF 来开启/关闭网络模拟了.

Network Link Conditioner

Network Link Conditioner 可以模拟多种网络环境,以下是该工具目前自带的:

  • EDGE
  • 3G
  • DSL
  • WiFi
  • High Latency DNS
  • Very Bad Network
  • 100% Loss

每种情况都是通过设置上载、下载的 带宽, 延迟, 和 丢包率 (如果设置为 0, 即不影响你当前的网络环境,本来网络是什么样子就是什么样子).

Preset

你也可以自建配置一个环境,在 【Manage Profiles…】里面自定义

wifi

如果是 IOS 的话是可以直接用苹果提供的开发者模式连上该网络(详细见:http://nshipster.com/network-link-conditioner/),不过博主这里用的是安卓测试机,所以就通过 mac 的网络共享来建一个 wifi (详见:Mac共享无线网络)然后用安卓机连上 mac 建的 wifi 就好了。

接下来就可以通过切换各种网络环境来测试应用了。切换功能在下面这个面板上:

Network Link Conditioner

mac osx 应用跨屏幕

一般情况下 mac osx 中一个应用程序只能在一个屏幕上显示,作为从 windows 转过来的用户有点不太习惯,Goolge 后发现还是有解决方案的(虽然不是很好用)。

打开 Mac 的系统偏好设置 -> Mission Control -> 勾掉 显示器具有单独的 Spaces (Displays have separate Spaces)然后注销一下,这样一个应用的窗口就可以跨多个屏显示了。

详细见 http://arstechnica.com/apple/2013/10/os-x-10-9/11/

Github rebase

首先前提是是在 github 上参与多人的项目。因为每个人写的提交的时间不一致,互相之间的提交可能参差不齐,不方便阅读。
所以为一个多人项目写好某个功能点的时候,或者是你 fork 了某个项目想要 PR 给原项目的时候,最好能够把提交的记录都各自重排一下,把各自一块的提交排列到一起,保持历史记录的可读性。

为了做到这个,在开发新功能的时候最好先建一个新的分支。在分支上写好之后(这个时间跨度不一定),先把原项目上最新的代码拉去到本地。然后再切换到你开发新功能的分支进行 rebase。

git checkout master # 确保你当前在 master 中
git remote add author git@github.com:你参加的库作者/库名.git # 将PR目标项目以 author 的别称记录在本地
    # 只需添加一次, 未来可以复用
git fetch author # 获取目标库最新代码
git status # 常看一下当前是不是在 master 中
git merge author/master  # 将目标库最新的代码合并到你的 master 中
git checkout your-branch # 切换到你的分支库中
git rebase master        # 参照最新的代码开始重排
git push origin your_branch # 你可能需要使用 `-f` 标志,如果需要的话加上 `--all`

其中拉取最新的代码也可以直接用 github 的 pull request 功能,将别人的代码拉取给自己。

另外,一些老外的教程里面也喜欢把上述代码中的 author 改名字叫 upstream。博主个人看法是:Anyway,名字随你改。

过程说起来有点简单,不过博主原来一直不清楚为什么要 rebase,还以为 rebase 是为了去掉 “Merge pull request #xx from xxxx/xxxxx” 这种没什么意义的东西。真是黑历史,来 mark 一下。

node.js Error: EBADF, write

最近工作写了个小项目,本以为能好好喝下茶,可是让人想掀桌的报错出现了。

fs.js:77
      throw err;  // Forgot a callback but don't know where? Use NODE_DEBUG=fs
            ^
Error: EBADF, write
    at Error (native)

WTF?! fs.js: 77? 这是要我去看内核源代码?好在项目还不大,拆拆更健康。花了半天的时间之后大概是清楚了。

首先是 Error 的名字 EBADF 其意义是 bad file descriptor 错误的文件描述符。
Error: EBADF, write 表示往错误的文件描述符里面写数据了。

出现这个BUG的场景简而言之,是有一个 .on(‘data’) 事件拿到数据往 fd 里面写,这个时候某个操作抛了 error 我在处理error 的时候 close 了这个 fd,而另外一边去还在触发 data 事件想往这个(已经被我 close 的)fd里面写数据。如下:

// ...

var fd = fs.openSync(path, 'w');

test.on('data', function(data) {
	fs.write(fd, data);
});

test.on('end', function() {
	fs.close(fd);
});

// 在 end 之前 close 就会出现 Error: EBADF, write
setTimeout(function() {
	fs.close(fd);
}, 10);

// ...

解决方案:所以我们排查好出现 fs.close 关闭文件描述符的地方,确保 close 之后不会再有 read/write

Error: EBADF, close

另外附上在谷歌的过程中看到了另外一个类似的错误。这是当你为多种情况做 fs.close(fd); 的处理,然而不幸的是,多个情况被都触发, fs.close(fd) 调用了多遍,同样也会出现 EBADF 错误。这样就能出现:


test.on('end', function() {
	fs.close(fd);
	fs.close(fd); // 多调用了一次就会出现
});

不友好的报错

fs.js:77
      throw err;  // Forgot a callback but don't know where? Use NODE_DEBUG=fs
            ^
Error: EBADF, close
    at Error (native)

解决方案:依旧是排查 fs.close,只不过这次是要保证多种处理不会反复执行 fs.close ,或者你可以使用 try/catch 来无视它。

Error: EBADF, bad file descriptor

最后,当 fd 失效以后进行 read 操作的话,我还以为会出现 Error: EBADF, read 结果并没有。以下是尝试出现BUG的代码:

// ...

fs.closeSync(fd);
fs.readSync(fd, new Buffer(1024), 0, 1024);

// ...

不过这个报错会友好很多,有将其调用栈打出来。

fs.js:552
  var r = binding.read(fd, buffer, offset, length, position);
                  ^
Error: EBADF, bad file descriptor
    at Error (native)
    at Object.fs.readSync (fs.js:552:19)
    at command.<anonymous> (/Users/Lellansin/Documents/workspace/node/test-server/app/services/TestService.js:40:6)
    at command.emit (events.js:110:17)
    at ChildProcess.emit (events.js:129:20)
    at maybeClose (child_process.js:1015:16)
    at Socket.<anonymous> (child_process.js:1183:11)
    at Socket.emit (events.js:107:17)
    at Pipe.close (net.js:485:12)

解决方案:看错误栈去改代码就好了。。

node.js Error: stdout maxBuffer exceeded

在使用 child_process 模块中的 exec 、execFile、spawnSync、execFileSync、execSync 方法时需要注意其 options 参数中的 maxBuffer 项。

以上方法在执行时会在内存中建一个 buffer 来缓冲组合所有的输出数据,而 maxBuffer 则是指定该 buffer 大小的地方。如果输出超过指定的大小则会报 maxBuffer exceeded 的错误。

解决方案是执行的时候估计好大小,设置更大的 maxBuffer:

var exec = require('child_process').exec;

var child = exec('ls -lah', {
	encoding: 'utf8',
	timeout: 0,
	maxBuffer: 5000 * 1024, // 默认 200 * 1024
	killSignal: 'SIGTERM'
}, function(err, stdout, stderr) {
	console.log(stdout);
});

或者是用 spawn 的 .on(‘data’) 事件触发时,手动拼接数据到 .on(‘close’) 事件触发的时候获得完整数据。

C/C++ 使用 memoization 优化算法

memoization 是一种缓存计算结果,避免重复计算,用空间换时间的优化方式。

以常见的斐波那契数列计算为例:

#include <stdio.h>

#define COUNT_TIMES 10

int fib(int n)
{
    if (n == 0 || n == 1)
    {
        return 1;
    }
    else
    {
        return fib(n - 2) + fib(n - 1);
    }
}

int main()
{
    int i;
    for (i = 0; i < COUNT_TIMES; i++)
        printf("fib %dn", fib(i));
}

输出:

fib 1
fib 1
fib 2
fib 3
fib 5
fib 8
fib 13
fib 21
fib 34
fib 55

实际上,我们来看看其中的计算次数

#include <stdio.h>

#define COUNT_TIMES 10

int count;

int fib(int n)
{
    if (n == 0 || n == 1)
    {
        return 1;
    }
    else
    {
        count++;
        return fib(n - 2) + fib(n - 1);
    }
}

int main()
{
    int i, *mem;

    for (i = 0; i < COUNT_TIMES; i++)
    {
        printf("n %d 结果 %2d 计算次数 %dn", i, fib(i), count);
        count = 0;
    }
}

结果:

n 0 结果  1 计算次数 0
n 1 结果  1 计算次数 0
n 2 结果  2 计算次数 1
n 3 结果  3 计算次数 2
n 4 结果  5 计算次数 4
n 5 结果  8 计算次数 7
n 6 结果 13 计算次数 12
n 7 结果 21 计算次数 20
n 8 结果 34 计算次数 33
n 9 结果 55 计算次数 54

我们发现实际上计算的次数跟其结果相当,计算 n 的斐波那契数列其计算量就是 fib(n) – 1 次了。想想也是醉了。

那么让我们使用 memoization 来优化一下:

#include <stdio.h>
#include <stdlib.h>

#define COUNT_TIMES 10

int count;

int fib(int n, int *mem)
{
    // 如果没有缓存结果则进行计算,并把结果加入到缓存中
    if (mem[n] == -1)
    {
        mem[n] = fib(n - 1, mem) + fib(n - 2, mem);
        // 统计计算次数
        count++;
    }
    // 返回缓存结果
    return mem[n];
}

int main()
{
    int i, j, *mem;
    for (i = 0; i < COUNT_TIMES; i++)
    {
        // 申请一块内存来缓存结果
        mem = (int *)malloc(sizeof(int) * COUNT_TIMES);
        // 初始化其中的结果
        mem[0] = mem[1] = 1;
        for (j = 2; j < COUNT_TIMES; j++)
            mem[j] = -1;

        // 调用计算
        printf("n %d 结果 %2d 计算次数 %dn", i, fib(i, mem), count);

        count = 0; // 计算次数清零
        free(mem); // 释放用来缓存的内存
    }
}

优化之后,可以发现时间复杂度很轻松的变成 O(n) 了

n 0 结果  1 计算次数 0
n 1 结果  1 计算次数 0
n 2 结果  2 计算次数 1
n 3 结果  3 计算次数 2
n 4 结果  5 计算次数 3
n 5 结果  8 计算次数 4
n 6 结果 13 计算次数 5
n 7 结果 21 计算次数 6
n 8 结果 34 计算次数 7
n 9 结果 55 计算次数 8

优化之后,当 n = 15,速度大概是原版的1000倍,当 n = 27 速度大概是原来的 10000 倍了。应该说重复计算的计算量越大使用 memoization 获得的效果就越明显,不过实际应用中要考虑到其所消耗的内存是否值得,也就是看一个性价比吧。

最后去掉注释简单封装一下。

#include <stdio.h>
#include <stdlib.h>

#define COUNT_TIMES 10

int * init_mem() {
    int i, *mem;
    mem = (int *)malloc(sizeof(int) * COUNT_TIMES);
    mem[0] = mem[1] = 1;
    for (i = 2; i < COUNT_TIMES; i++)
        mem[i] = -1;
    return mem;
}

int fib(int n, int *mem)
{
    if (mem[n] == -1)
        mem[n] = fib(n - 1, mem) + fib(n - 2, mem);
    return mem[n];
}

int main()
{
    int i, *mem;

    for (i = 0; i < COUNT_TIMES; i++)
    {
        mem = init_mem();
        printf("fib %dn", fib(i, mem));
        free(mem);
    }
}