Coder Social home page Coder Social logo

creamidea.github.com's Introduction

creamidea.github.io

1 Articles

2 LICENSES

3 Content

/creamidea.github.com
|-- README.org
|-- _cv ;; your cv
|-- _draft ;; your draft
|-- _content ;; where your articles putting directory
    |-- articles ;; articles
    `-- wiki ;; wiki
`-- static
    |-- CNAME ;; Github CNAME
    |-- about ;; about me
    |-- about.html ;; about me
    |-- app.js ;; main js
    |-- style.css ;; main css
    |-- favicon.ico ;; site's favicon
    |-- medium-fonts.css ;; font
    |-- morecomm.gif ;; comment's picture (click)
    `-- img ;; git lfs track "*.png"
       |-- avatar.png ;; your avatar
       `-- foundingfather_v2.png ;; 

4 Reading Guide

  • Back(browser) -> Back History
  • Forward(browser) -> Forwad History
  • Fn + Left (Key: Home) -> To the top
  • Fn + Right (Key: End) -> To the bottom

4.1 Shortcuts(Not Supportting Now)

  • H -> Home
  • A -> Archive
  • I -> About Me
  • R -> RSS
  • / -> Search

5 Basic information

Edited by Emacs. Generated by Org-mode. Hosted by Github.

6 Live IN

English:The Future World

中文:现实生活

日本語(にほんご):二次元(にじげん)の世界(せかい)

ελληνικά:χρόνος διάστημα

Elisp MIT-SCHEME C {JavaScript/CoffeeScript CSS HTML} Nodejs Python Django

Ubuntu12.04 Windows7 OSX

Emacs Latex Org-mode Git GIMP

Astronomy

7 About Me

没有自我描述,你可以从我的字里行间体会我是一个怎样的人。或者直接Email Me

You can know me from my words or Email Me.

2015-12-26 蜂蜜甜甜圈加入。

8 NICKNAME

  • ICECREAM(氷菓)
  • CREAMDIEA
  • NEKOTREK
  • Ni Junjia
  • Junjia Ni
  • Qooni

creamidea.github.com's People

Contributors

creamidea avatar icecream avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

creamidea.github.com's Issues

Rust 外部依赖和被链接

Rust 有极强的跨平台性,对比 c/c++,又能解决野指针的问题,还能解决类型安全的问题。
所以,使用 Rust 编译出来的程序,可以以库的形式和其它程序进行连接,是不错的未来。

如果是编写 Nodejs 扩展,https://github.com/napi-rs/node-rs 是一个不错的开始。
如果是编写 wasm 项目,https://github.com/rustwasm/wasm-pack 是一个不错的开始。

本文仅仅从较低层面,讲解如何编译动态库,并在 c 和 rust 之间调用。

C 语言使用 Rust 函数

示例 c-app

第一步,配置 rust Cargo.tomal,使其输出 cdylib

[lib]
name = "hello"
path = "src/lib.rs"
crate-type = ["cdylib"]

第二步,编写 rust 代码,使用 extern 声明导出的函数。使用 cargo build 编译产出库文件 libhello.so,位置在 target/debug 文件夹下。(仅用于测试,如果是用于生产,请带上 --release)

#[no_mangle]
pub extern "C" fn sum(a: i32, b: i32) -> i32 {
    a + b
}

第三步,编写 c 代码,引用 rust 写的功能函数

// hello.h
#ifndef HELLO_H_
#define HELLO_H_

// 声明 sum 来自外部库
extern int sum(int, int);

#endif

// hello.c
#include <stdio.h>
#include "hello.h"

int main(int argc, char const *argv[])
{
    int result = sum(1, 2);
    printf("result: %d\n", result);
    return 0;
}

第四步,编译 c 代码,链接 Rust 动态链接库

  • -l 指定链接时需要的动态库,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库
  • -L 依赖库搜索路径
gcc hello.c -o hello -lhello -L../target/debug

Rust 使用 C 函数

示例 c-lib

第一步,编写 C 代码

#include <stdio.h>

typedef void (*rust_callback)(int);
rust_callback cb;

int register_callback(rust_callback callback) {
    printf("register_callback...\n");
    cb = callback;
    return 3;
}

void trigger_callback() {
  cb(7); // Will call callback(7) in Rust.
}

第二步,编译

# 动态链接
# -fPIC 和 -shared 可以编译出动态链接库
gcc -fPIC -shared -o libext.so ext.c

# 静态链接
gcc -o ext.o -c ext.c
# -c 不要编译为可执行文件,只编译为目标文件
ar -cvr libext.a ext.o

第三步,编写 Rust 代码,引用 C 函数

extern fn callback(a: i32) {
    println!("I'm called from C with value {0}", a);
}

// 使用 link 宏,引用 C 函数。ext 为编译出来的库名称(一般不写 lib 前缀)
// 可以通过指定 kind,告诉 rustc 是使用 动态链接库 还是 静态链接库
// 动态链接库 #[link(name = "ext")]
// 静态链接库 #[link(name = "ext", kind = "static")]
// https://doc.rust-lang.org/nomicon/ffi.html#linking
// 这里的 extern 类似于接口,定义内部函数的接口,Rust 代码调用时,需要提供接口的实现。
#[link(name = "ext")]
extern {
   fn register_callback(cb: extern fn(i32)) -> i32;
   fn trigger_callback();
}

fn main() {
    unsafe {
        let result = register_callback(callback);
        println!("result from c: {}", result);
        trigger_callback(); // Triggers the callback.
    }
}

第四步,编写 build.rs。需要注意的是:
对于链接动态链接库,只需要配置
println!(r"cargo:rustc-link-search=./c-lib");

对于静态链接库,需要再增加配置(或者在 rust 代码里面 link 指定 kind 为 static 即可)
https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library
println!(r"cargo:rustc-link-lib=static=ext");

fn main() {
    // 制定搜索路径
    println!(r"cargo:rustc-link-search=./c-lib");

    // 如果是动态链接库,在 macOS 上,编译成功之后,需要配置 DYLD_LIBRARY_PATH
    // export DYLD_LIBRARY_PATH=.:$DYLD_LIBRARY_PATH
    // 此时执行可执行文件才能成功。否则会提示 dyld: Library not loaded

    // 待确定:如果是静态链接库,可以配置如下参数。编译成功之后,可以直接执行
    // println!(r"cargo:rustc-link-lib=static=ext");

    // 有兴趣可以试试 crate cc  https://crates.io/crates/cc
    // --crate-type=cdylib --crate-name=ext ext.c
    // Tell Cargo that if the given file changes, to rerun this build script.
    // println!("cargo:rerun-if-changed=c-lib/ext.c");
    // Use the `cc` crate to build a C file and statically link it.
    // cc::Build::new()
    //     .file("src/hello.c")
    //     .compile("hello");
}

其它文章参考

第五步,执行。对于动态链接库,需要配置 DYLD_LIBRARY_PATH(macOs) / LD_LIBRARY_PATH(Linux) 才能正常执行。否则提示 dyld: Library not loaded

另外,如果是静态编译,那么需要确保库所在位置只有静态库,不能有同名的动态库

cargo run
# 或者
cargo build
target/debug/xx

rlib

如果是 rust 编译出来的库,则可以参考下面的例子。
使用 extern crate 导入。并在编译的时候,通过 --extern 参数指定依赖库的名称和地址

// rustc hello.rs --extern hello=../target/debug/libhello.rlib -o hello
extern crate hello;

fn main() {
    let a = 1;
    let b = 2;
    let c = hello::sum(a, b);
    println!("{a} + {b} = {c}");
}

Debug PHP

Using PHPStorm and Built-in Server

  1. install xdebug
  2. Config PHPStorm:
    • Add Built-in Server: Preferences > Languages & Framework > +
    • Configurations: Add PHP Built-in Server
  3. Click Start (and telephone icon?)

2021 碎碎念

雄关漫道真如铁,而今迈步从头越。

回顾 2021

除开本职工作,平时的一些活动和思考记录。

1月-5月 和小伙伴们做了一个类似 scrimba 的视频教育网站试水。期间也和发小讨论构建审计相关的平台和工具的可行性。

6月-10月 整一些关于 Serverless 方面的资料,想基于此做一个类似 Nextjs 的框架。目前还没有实现。

11月-12月 相关源码学习,重点研读 Reactjs 源码和 Nodejs 源码

其他值得记录的文章和笔记

尾声

年末,和同事探讨团队内搭建未来的路:从前端视角来看,搭建并不是解决前端生产力的银弹,搭建是通过赋能研发链路中其他角色,从而实现解放生产力的工具。但是有可以坦然接受的「赋能」对象吗?

基于 Homebrew 的 bottle 机制在 Catanalina 安装 tmux

tmux 的 bottle

前往 https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/ 在页面查找 tmux
image

使用 otool 工具查看动态依赖的动态链接库
image

动态链接库可以继续在 https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/ 上查找。找到之后解压,将其中的 lib 文件夹按照 /usr/local/opt/[lib-name] 的形式存放,比如

image

接下来使用 install_name_tool 命令进行动态链接库地址修改。

install_name_tool -change @@HOMEBREW_PREFIX@@/opt/utf8proc/lib/libutf8proc.2.dylib /usr/local/opt/utf8proc/lib/libutf8proc.2.dylib ./tumx
install_name_tool -change @@HOMEBREW_PREFIX@@/opt/ncurses/lib/libncursesw.6.dylib /usr/local/opt/ncurses/lib/libncursesw.6.dylib
install_name_tool -change @@HOMEBREW_PREFIX@@/opt/libevent/lib/libevent_core-2.1.7.dylib /usr/local/opt/libevent/lib/libevent_core-2.1.7.dylib

到这一步,tmux 程序已经可以运行,但是无法正常创建 session。出现如下错误 can't find terminfo database

解决 can't find terminfo database

最终问题通过这种方式解决:https://unix.stackexchange.com/a/677744
在 .zshrc 里面增加 export TERMINFO='/usr/share/terminfo/',可以顺利解决该问题

image

错误日志输出

运行 tmux -vvv -Ltest -f/dev/null new,可以在当前路径下获得如下日志文件
image

[Webpack]Error: You may need an appropriate loader to handle this file type.

记录一次在 React 开发的过程中出现下面错误的问题:

ERROR in ./web-src/app.jsx
Module parse failed: C:\Users\xxxyyy\Repository\MemorizingWords\web-src\app.jsx Unexpected token (7:2)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (7:2)
    at Parser.pp$4.raise (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:2221:15)
    at Parser.pp.unexpected (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:603:10)
    at Parser.pp$3.parseExprAtom (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1822:12)
    at Parser.pp$3.parseExprSubscripts (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1715:21)
    at Parser.pp$3.parseMaybeUnary (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1692:19)
    at Parser.pp$3.parseExprOps (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1637:21)
    at Parser.pp$3.parseMaybeConditional (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1620:21)
    at Parser.pp$3.parseMaybeAssign (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1597:21)
    at Parser.pp$3.parseExprList (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:2165:22)
    at Parser.pp$3.parseSubscripts (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1741:35)
    at Parser.pp$3.parseExprSubscripts (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1718:17)
    at Parser.pp$3.parseMaybeUnary (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1692:19)
    at Parser.pp$3.parseExprOps (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1637:21)
    at Parser.pp$3.parseMaybeConditional (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1620:21)
    at Parser.pp$3.parseMaybeAssign (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1597:21)
    at Parser.pp$3.parseExpression (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1573:21)
    at Parser.pp$1.parseStatement (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:727:47)
    at Parser.pp$1.parseTopLevel (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:638:25)
    at Parser.parse (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:516:17)
    at Object.parse (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:3098:39)
    at Parser.parse (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack\lib\Parser.js:902:15)
    at DependenciesBlock.<anonymous> (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack\lib\NormalModule.js:104:16)
    at DependenciesBlock.onModuleBuild (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack-core\lib\NormalModuleMixin.js:310:10)
    at nextLoader (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack-core\lib\NormalModuleMixin.js:275:25)
    at C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack-core\lib\NormalModuleMixin.js:259:5
    at Storage.finished (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:38:16)
    at C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\graceful-fs\graceful-fs.js:78:16
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:380:3)

跟着 webpack 的代码走,慢慢排查出问题代码出现在这里console.log + grep大法好):
在正则匹配的时候,因为操作系统对于路径的不同表示,导致 this.matchPart(str, obj.include) 会出现不同的结果。

于是有两种修改方式:

  • 修改业务层代码

将业务代码写出这个样子:

module: {
  loaders: [
    {
      test: /\.jsx$/,
      exclude: /node_modules/,
      include: **path.resolve(__dirname, 'web-src')**,
      loader: "babel",
      query: {
        // plugins: ['transform-runtime', 'transform-react-jsx'],
        cacheDirectory: true,
        presets: ["es2015", "react"]
      }
    }
  ];
}

重点:将 include: __dirname+'/web-src' 改成 path.resolve(__dirname, 'web-src')

  • 改 webpack 代码
if(typeof test === "string") {
  return regExpAsMatcher(new RegExp("^"+test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")));
}

改成

var os = require('os')
...
if(typeof test === "string") {
  test = /windows/i.test(OS.type()) ? test.split('/').join('\\') : test;
  return regExpAsMatcher(new RegExp("^"+test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")));
}

最后,从这个错误中我们可以学习到,在写跨平台的操作系统的时候,对于路径问题的处理是需要小心谨慎的。

JSX 是声明式“语言”

最近,碰到一个更新问题,让我对 JSX 是声明式的有了更深的理解。问题后续有空详细解释,现在简单说一下。

问题场景是,如下 JSX 描述的 UI

<Layout>
  <Tab>
    <TabItem><Table /></TabItem>
    <TabItem><Table /></TabItem>
  </Tab>
</Layout>

在 Layout 这个组件内部会 clone children,在 clone 前会找到 Table,并改变其属性,然后 clone。问题在于,改变属性并不能使 Table 重新渲染,「按理」来说,如果 Table 属性变化,会在 reconciler 阶段被识别为 Update,在 commit 阶段进行「渲染」。为什么?

因为这里的 Tab 组件也会处理其 children,如果内部只对第一次渲染处理,所有后续渲染全部 return 之前的 children(这个例子很极端,只是让大家更好的理解)。那么你再怎么处理 Table,也不会使 Table 被重新渲染(因为在 React 看来,Tab 后续的 children 都没有变化)。这里的 Tab JSX 只是描述了这样的 UI,也就是所谓的声明式,Tab 内部有一套自己的处理方式,处理完成的结果才会给 React 去 reconciler,而不是你在 Layout 内部处理的之后的 children。React 只会相应 Layout 第一层,比如这里的 Tab。

这里使用的 Tab 来自 fusion 组件库,这行代码,类似做了一次缓存(返回了空对象),导致如果 Tab 标签页不进行切换,那么就没办法更新其 children,也就是上面说的 Table。

React 事件处理

事件绑定

从 createRoot 函数开始

function createRoot(container) {
  ...

  const rootContainerElement: Document | Element | DocumentFragment =
  container.nodeType === COMMENT_NODE
    ? (container.parentNode: any)
    : container;
  listenToAllSupportedEvents(rootContainerElement);

  ...
}

listenToAllSupportedEvents 进行原生事件绑定。也会处理 nonDelegatedEvents,selectionchange 事件等

function listenToAllSupportedEvents() {
  // 核心代码,listenToNativeEvent 调用 addTrappedEventListener
  // eventSystemFlags |= IS_CAPTURE_PHASE;
  listenToNativeEvent(domEventName, true, rootContainerElement);
}

addTrappedEventListener 函数根据不同事件,构造不同优先级的处理函数(createEventListenerWrapperWithPriority),得到该函数之后进行原生 DOM 事件绑定,绑定到根 DOM 节点。比如,click 事件,处理函数就是 dispatchDiscreteEvent,默认是 dispatchEvent。并通过 bind 的方式,固定参数:domEventName、eventSystemFlags、targetContainer(即 root dom 节点实例)

至此,绑定原生事件的过程结束,后续就是等待用户交互,触发原生事件,进入诸如 dispatchEvent 函数进行处理。

事件处理

进入诸如 dispatchEvent 函数进行处理。其最总调用 dispatchEventOriginal 函数处理。

function dispatchEventOriginal() {
  ...
  const blockedOn = findInstanceBlockingEvent(
    domEventName,
    eventSystemFlags,
    targetContainer,
    nativeEvent,
  );
  if (blockedOn === null) {
    dispatchEventForPluginEventSystem(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      return_targetInst,
      targetContainer,
    );
    ...
    return;
  }
  ...
}

function findInstanceBlockingEvent() {
  // 该值会在函数中处理,有值的 2 种情况
  // - targetInst NearestMountedFiber 是 Suspense
  // - Hydrating 情况下,targetInst NearestMountedFiber 是 HostRoot
  return_targetInst = null;

  const nativeEventTarget = getEventTarget(nativeEvent);

  // targetInst 是根据当前事件的 DOM 节点中存储的 internalInstanceKey 找到对应 fiber 实例
  let targetInst = getClosestInstanceFromNode(nativeEventTarget);

  ...

  return null
}

blockedOn 有值的 2 种情况,有值则会调用 queueDiscreteEvent 放入到 queuedDiscreteEvents 队列(retryIfBlockedOn 会消费)

  • targetInst NearestMountedFiber 是 Suspense
  • 注入 Hydrating 情况下,targetInst NearestMountedFiber 是 HostRoot
  • 注:targetInst 是根据当前事件的 DOM 节点中存储的 internalInstanceKey 找到对应 fiber 实例
function dispatchEventForPluginEventSystem() {
  // 注意,这里的 targetInst 是👆说的 return_targetInst 这个全局变量
  // 即处理 Suspense 或者 HostRoot 情况
  if (targetInst !== null) {
    ...
  }
  
  batchedUpdates(() =>
    dispatchEventsForPlugins(
      domEventName,
      eventSystemFlags,
      nativeEvent,
      ancestorInst,
      targetContainer,
    ),
  );
}

dispatchEventsForPlugins 函数就是调用注册的 Plugin 的 extract 方法,提取事件。比如 SimpleEventPlugin.extractEvents 函数内会通过 accumulateSinglePhaseListeners 函数收集绑定的事件,并构造 SyntheticEvent,一起放入到 dispatchQueue 队列

function extractEvents() {
  ...
  const listeners = accumulateSinglePhaseListeners(
    targetInst,
    reactName,
    nativeEvent.type,
    inCapturePhase,
    accumulateTargetOnly,
    nativeEvent,
  );
  if (listeners.length > 0) {
    // Intentionally create event lazily.
    const event = new SyntheticEventCtor(
      reactName,
      reactEventType,
      null,
      nativeEvent,
      nativeEventTarget,
    );
    dispatchQueue.push({event, listeners});
  }
  ...
}

function accumulateSinglePhaseListeners() {
  // Accumulate all instances and listeners via the target -> root path.
}

dispatchQueue 队列会在 processDispatchQueue 函数内被处理

function processDispatchQueueItemsInOrder(
  event: ReactSyntheticEvent,
  dispatchListeners: Array<DispatchListener>,
  inCapturePhase: boolean,
): void {
  let previousInstance;

  if (inCapturePhase) {
    // 处理捕获事件
    for (let i = dispatchListeners.length - 1; i >= 0; i--) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  } else {
    // 处理冒泡事件
    for (let i = 0; i < dispatchListeners.length; i++) {
      const {instance, currentTarget, listener} = dispatchListeners[i];
      if (instance !== previousInstance && event.isPropagationStopped()) {
        return;
      }
      executeDispatch(event, listener, currentTarget);
      previousInstance = instance;
    }
  }
}

How to export virtualenv

in virtual env
export:

(env_name) pip freeze > requirements.txt

import:

$ virtualenv <env_name>
$ source <env_name>/bin/activate
(<env_name>)$ pip install -r path/to/requirements.txt

You can use this script to auto complete:

def pip_install():
    with cd(env.path):
        with prefix('source venv/bin/activate'):
            run('pip install -r requirements.txt')

Reference
How to export virtualenv? - The best answer

Error: 400 bad request ('xxxxx') when using Uwsgi to connect Nginx and Djanog

This is a disgust problem!!!

One way of solving this problem is to watch the error.log

First, you should know which encode the system is using. You can use locale to look at.
Second, make sure your nginx is using the same code as the system. You can put this in nginx.conf

http {
    ...
    charset utf-8;
    large_client_header_buffers 4 16k; # maybe
    ...
}

At last, make sure your Django(Python) using the same code as the nginx. You can put this code in your /your/path/to/lib/python2.7/sitecustomize.py.

# encoding=utf8
import sys

reload(sys)
sys.setdefaultencoding("utf-8")

Good luck.

PS: uwsgi ini file format

[uwsgi]
# Django-related settings
# the virtualenv (full path)
# virtualenv    = /opt/weixnpy/
# the base directory (full path)
chdir = /home/www/xxxxx/wx/weixnpy
# Django's wsgi file
module          = weixnpy.wsgi
#module          = weixnpy.wsgi:application
env             = DJANGO_SETTINGS_MODULE = weixnpy.settings
home            = /opt/weixnpy/ # your virtual environment
# py-programname          = /usr/local/bin/python2.7
# pythonpath      = /home/www/icslg/wx/weixnpy/ 

# process-related settings
# master
master          = true
# maximum number of worker processes
processes       = 4
# the socket (use the full path to be safe
# socket          = 127.0.0.1:8001
socket          = /tmp/xxxxxxx.sock
# socket          = /var/run/weixn.sock
# ... with appropriate permissions - may be needed
chmod-socket    = 664
chown-socket = www:www
# clear environment on exit
vacuum          = true
logto           = /var/log/uwsgi.log

react hydrate 模式

快速预览

react hydrate 是指,在 SSR 模式下渲染出 html 字符串发送到浏览器渲染之后,绑定监听事件以及将 react Fiber 树构建出来的过程,也就是所谓的「水合」,或者叫做「注水」。

一个简单的例子🌰

import { createElement } from "react";
import {
  renderToString,
} from "react-dom/server";

import http from "http";

const hello = createElement(
  "div",
  {
    onClick: () => {
      console.log("hello");
    },
  },
  "Hello World"
)

const server = http.createServer((req, res) => {
  const html = renderToString(
    hello
  );

  res.end(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
        <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    </head>
    <body>
        <div id="root">${html}</div>
        <script>
          const { createElement } = window.React;
          const { hydrateRoot, createRoot } = window.ReactDOM;
          const root = hydrateRoot(
            document.getElementById('root'),
            createElement(
              "div",
              {
                onClick: () => {
                  console.log("hello");
                },
              },
              "Hello World"
            ),
          );
        </script>
    </body>
    </html>`);
});

server.listen(3001);

大致过程:通过 renderToString 等方法,将 React 组件代码处理成 HTML(Fiber 树 => HTML,去掉所有绑定事件,只留 HTML 信息),返回浏览器或者搜索引擎,实现 SSR 的效果。客户端 React 也不需要再一次处理 DOM 元素,只需要绑定的事件,处理 Fiber 树等即可。一举两得。

重点就在下面的代码里面,调用 hydrateRoot 时第二个参数,也就是把 React 组件重新「执行」一下。这里的 React 组件要和服务端 SSR 时放入的组件一致。

<script>
  const { createElement } = window.React;
  const { hydrateRoot, createRoot } = window.ReactDOM;
  const root = hydrateRoot(
    document.getElementById('root'),
    createElement(
      "div",
      {
        onClick: () => {
          console.log("hello");
        },
      },
      "Hello World"
    ),
  );
</script>

下面进入代码分析阶段,回答 2 个问题:

  1. 事件什么时候绑定
  2. Fiber 树如何被构建出来,HostComponent 的 fiber.stateNode 何时被赋值?

事件什么时候绑定

hydrateRoot 函数内部会调用 listenToAllSupportedEvents(container),这里的 container 就是 DOM 容器节点 。listenToAllSupportedEvents 函数就是处理委托事件的开始入口,具体实现可以看这篇文章

Fiber 树构建

承接上段,hydrateRoot 函数内部会执行如下创建逻辑,进入 Fiber 树构造。最大的区别在于,跳过 DOM 树的创建和增加。

  • createHydrationContainer (createFiberRoot: FiberRootNode, createHostRootFiber)
    createFiberRoot 的第 3 个参数被赋值为 true,最终影响该数据结构
const initialState: RootState = {
  element: initialChildren,
  isDehydrated: hydrate,
  cache: (null: any), // not enabled yet
  transitions: null,
};
uninitializedFiber.memoizedState = initialState;
  • scheduleInitialHydrationOnRoot,和 scheduleUpdateOnFiber 相比,代码更加简单,标记 lanes,就直接进入 ensureRootIsScheduled 阶段
export function scheduleInitialHydrationOnRoot(
  root: FiberRoot,
  lane: Lane,
  eventTime: number,
) {
  const current = root.current;
  current.lanes = lane;
  markRootUpdated(root, lane, eventTime);
  ensureRootIsScheduled(root, eventTime);
}

以优先级 16 开启更新

开始更新时,使用默认优先级 16,React18 版本下会进入并发,但不会分片。(16 是在 BlockingLane 范围内)

Render 阶段

updateHostRoot

在 render 阶段的 beginWork 阶段处理 HostRoot 的 updateHostRoot 函数内会对 isDehydrated 判断。如果是真值,会更新 updateQueue.baseState 和 workInProgress.memoizedState

// Flip isDehydrated to false to indicate that when this render
// finishes, the root will no longer be dehydrated.
const overrideState: RootState = {
  element: nextChildren,
  isDehydrated: false,
  cache: nextState.cache,
  transitions: nextState.transitions,
};
const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
// `baseState` can always be the last state because the root doesn't
// have reducer functions so it doesn't need rebasing.
updateQueue.baseState = overrideState;
workInProgress.memoizedState = overrideState;

以及调用 enterHydrationState 函数:获取第一个 DOM 节点并赋值全局变量 nextHydratableInstance

enterHydrationState(workInProgress);

const parentInstance: Container = fiber.stateNode.containerInfo;
nextHydratableInstance = getFirstHydratableChildWithinContainer(
	parentInstance,
);

updateHostComponent

在 render 阶段的 beginWork 阶段处理 HostComponent 的 updateHostComponent 函数,会将已经存在的 DOM 实例和子 fiber 进行关联,也就是赋值 fiber.stateNode,通过 tryToClaimNextHydratableInstance 完成,tryHydrate 函数完成具体的赋值 stateNode 操作。

在 render 阶段的 completeOfWork 内处理 HostComponent 分支时,下面的语句返回真值,进入「水合」处理,判断当前节点是否存在更新

var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
  if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
    markUpdate(workInProgress);
  }
} else {
...
}

Commit 阶段

HostRoot

根据 HostRoot 的 memoizedState.isDehydrated 为 true,会进入 commitHydratedContainer 逻辑,该逻辑会处理 queuedDiscreteEvents。

后续

处理所有副作用和生命周期,和正常的 Commit 阶段类似,不再赘述。


以上

React 开发总结

每个组件控制状态尽量少,不要将所有状态维护在一个组件

Context 需要读写分离处理

优先使用 ahooks 等社区优秀 Hooks 库提供的方法

合理使用 useEffect

  • 如果是用户主动触发的逻辑,写到事件函数里面
  • 如果是渲染之后(displayed)触发的逻辑,写到 useEffect 里面

合理使用 useCallback/useMemo 能不用就不用

函数组件都要命名名称,不要使用匿名函数

Bad:

export default function() {}

Good:

export default function Component() {}

函数组件的入参尽量使用结构的形式

function Component({ title, value }) {
  ...
}

更新多个状态,请合理抽象到一个对象。或者考虑使用 useReducer

Bad code:

const [count, setCount] = useState(0);
const [name, setName] = useState("");

const onClick = () => {
  setTimeout(() => {
    setName("John");
    setCount(count + 1);
  }, 1000);
};

Good code:

const [state, setState] = useState({
  count: 0,
  name: "",
});

const onClick = () => {
  setTimeout(() => {
    setState((prevState) => ({
      ...prevState,
      name: "John",
      count: prevState.count + 1,
    }));
  }, 1000);
};

不要在 JSX 里面写函数

Bad code:

return (
  <div>
    <button
      onClick={() => {
        setCount(1);
        // ...
      }}
    >
      Click
    </button>
  </div>
);

Good code:

const onClick = useCallback(() => {
  setCount(1);
  // ...
}, [deps]);

return (
  <div>
    <button onClick={onClick}>Click</button>
  </div>
);

使用 object(Map) 替代 switch

Bad code:

switch (props.type) {
  case "ADMIN":
    return <Admin />;
  case "USER":
    return <User />;
  default:
    return <NotFound />;
}

Good code:

const componentMap = {
  ADMIN: Admin,
  USER: User,
  NOT_FOUND: NotFound,
};

const Component = componentMap[props.type];
return <Component />;

Better code:

const componentMap = {
  ADMIN: React.lazy(() => import("../components/Admin")),
  USER: React.lazy(() => import("../components/User")),
  NOT_FOUND: React.lazy(() => import("../components/NotFound")),
};

const Component = componentMap[props.type];
return <Component />;

布尔类型,简短处理

Bad code:

return <button disabled={true}>Submit</button>;

Good code:

return <button disabled>Submit</button>;

如果没有子元素,就自闭处理

Bad code:

return <Component></Component>;

Good code:

return <Component />;

参考文档:

webpack 的 __webpack_public_path__ 工作原理

前置知识

JS 的全局变量和局部变量

Webpack 对 publicPath 的解释

Webpack 的 bootstrap 过程

记录

运行时,webpack bootstrap 代码中,对于需要加载的 JS/CSS 会增加 webpack_require.p 前缀,如

// manifest.js
    // ...

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "http://xx.com/public/"; // 赋值publicPath的值
    //...

/******/ 	script.src = __webpack_require__.p + "" + ({"0":"A","1":"B"}[chunkId]||chunkId) + ".js";
    // ...

编译时,webpack 会分析代码,如果识别到 __webpack_public_path__="http://xx.com/public/",则会处理成 __webpack_require__.p = "http://xx.com/public/"
(这里也解释了,为什么 window.__webpack_public_path__ 不起作用,原因就在这里:webpack 分析失败。)

// expr 数据结构
// https://juejin.cn/post/7062971609862111239
{
  type: "Identifier",
  start: 0,
  end: 100,
  loc: {
    start: { line: 1, column: 0 },
    end: { line: 1, column :100},
  },
  range:[0, 100],
  name: "__webpack_public_path__",
}

总结

__webpack_public_path__ 不是简单的 JS 变量,它会在 webpack 处理阶段被转码成

// 某模块内
function(module, __webpack_exports__, __webpack_require__) {
  // ...
  __webpack_require__.p = __webpack_public_path__ 配置的地址  
  // ...
}

这里也就没有全局变量、局部变量等概念,也不能画蛇添足,在 __webpack_public_path__ 前面增加 window

又因为 import 作用于提升,代码运行存在时机问题。为了保证 __webpack_public_path__ 配置生效,就必须要保证代码能在尽可能前的地方运行。所以最好的方式:
创建 public-path.js 文件,文件内容 __webpack_public_path__ = xxx。并且在工程的第一行引入 import 'public-path.js';

其他参考

自然双拼编码规则

 双拼是一种与现代汉语拼音方案几乎同时提出的一种拉丁化拼音文字方案,双拼方案在编码上不像全拼那样有长有短,而一律双码,即每个汉字的拼音都固定用两个字母表示。双拼方案的双码形式比较简短,符合韵律,可以快速输入。因此,越来越受到广大用户的青睐。

  “自然码输入系统”中包含了很多种常用编码方案的输入法,“自然双拼方案”是其中最简便快捷的压缩拼音,是在CCDOS压缩拼音的基础上补充设计而成的,将声母“zh”“ch”“sh”分别定义在“V”“I”“U”键上,然后将多个拼音字母组成的韵母压缩在一个键上,使得每个汉字的拼音都简化成了两个字母。

  自然双拼比其他双拼设计合理,并能与标准全拼和简拼最大程度兼容,是目前应用最多的双拼。包括微软双拼在内,都是以自然码双拼为基础的。

zrupjp

自然码双拼助记口诀

Zei Ying Qiu Nin Fen Ii W.ua.ia Ee     
贼 应  求 您 分 吃 蛙 鸭 鹅,    
T.ve Song Uu Lai Vui Ruan+ Mian Xie,    
特约 松  鼠 来 追 软   棉  鞋,    
C.iao (Y) Hang P.un.vn,Geng Kao B.ou J.an D.uang.iang.
此要 (外) 行 破文韵, 更 靠 笔偶 结案 得 汪  洋。

From: http://www.zrm.com.cn/new/udpn.htm

[Redux] this.props.dispatch is undefined in componentDidMount

When you use connect, please don't transfer mapDispatchToProps( the second optional param of connect), which overwrite this.props.dispatch in componentDidMount.

For example, the right way:

class ListContainer extends Component {

  constructor (props) {
    super(props)
  }

  componentDidMount () {
    const { dispatch } = this.props
    dispatch(fetchIssues(1, 25))
  }

  render () {
    return <IssueList {...this.props} />
  }
}

const VisibleIssueList = connect(
  mapStateToProps
// please don't transfer the `mapDispatchToProps `
)(ListContainer)

Windows Phone 8.0使用注意点

  1. Google Service Sync

    设置里面选择Google帐号,输入帐户和密码。如果你开启了两步验证,需要使用Google App的密码,就是Google生成的那个密码,

  2. Google联系人同步问题:

    not enough memory to sync error code 8007000E

    你看看是不是Google联系人中有些人的头像太大了,然后Windows Phone无法处理这么大的图片。删除这些人的头像,然后再试试。

  3. 安装软件

    如果你不怕麻烦,可以去试试破解之后的安装方式。如果你不想折腾的话,直接去注册一个微软帐号吧

React Array 检查 Key 原理

问题描述

有如下代码,静态编写多子组件,不会出现 key 警告

export function App({ name }) {
    return (
        <div>
            <h1>{name}</h1>
            <h1>{name}</h1>
            <h1>{name}</h1>
        </div>
    )
}

但是,如果使用动态的形式创建多个子组件,就会出现 key 警告

export function App({ name }) {
    return (
        <div>
            {
                [1,2,3].map(id => {
                    return <h2>{id}</h2>
                })
            }
        </div>
    )
}

原理探索

在新版 JSX 解析的过程中,开启如下配置
image

那么在分析 JSX 代码的时候,对于静态多个子组件,会对其父组件使用 jsxs。而对于动态版本,则会使用 jsx
image

jsxsjsx 函数定义在文件 packages/react-reconciler/src/ReactChildFiber.new.js
image

通过第四个参数控制,如果为 true,则会进入 validateChildKeys -> validateExplicitKey 提前设置 element._store.validated = true;。那么在 reconcileChildrenArray 阶段,warnForMissingKey 函数会在判断 !child._store || child._store.validated || child.key != null 提前返回(child._store.validated 为 true)

附 警告时调用堆栈

validateExplicitKey (index.js:formatted:2473)
validateChildKeys (index.js:formatted:2473)
jsxWithValidation (index.js:formatted:2473)  <- 这里调用时,第四个参数为 false
App (App5.js:6)
renderWithHooks (index.js:formatted:2473)
mountIndeterminateComponent (index.js:formatted:2473)
beginWork (index.js:formatted:2473)
beginWork$1 (index.js:formatted:2473)
performUnitOfWork (index.js:formatted:2473)
workLoopSync (index.js:formatted:2473)
renderRootSync (index.js:formatted:2473)
performSyncWorkOnRoot (index.js:formatted:2473)
scheduleUpdateOnFiber (index.js:formatted:2473)
updateContainer (index.js:formatted:2473)
(anonymous) (index.js:formatted:2473)
unbatchedUpdates (index.js:formatted:2473)
legacyRenderSubtreeIntoContainer (index.js:formatted:2473)
render (index.js:formatted:2473)
(anonymous) (index.js:10)
./src/index.js (index.js:24)
__webpack_require__ (bootstrap:856)
fn (bootstrap:150)
1 (reportWebVitals.js:14)
__webpack_require__ (bootstrap:856)
checkDeferredModules (bootstrap:45)
webpackJsonpCallback (bootstrap:32)
(anonymous) (main.chunk.js:1)

Who has seen the wind?

Christina G Rossetti

Who has seen the wind?
Neither I nor you.
But when the leaves hang trembling,
The wind is passing through.

Who has seen the wind?
Neither you nor I.
But when the trees bow down their heads,
The wind is passing by.

Python Import Problem

python 导入分为绝对路径导入和相对路径导入,之前一直不理解相对是怎么相对,直到看到这个:

image

也就是说,python xx.py 这里的 xx.py 就是主模块,假设代码如下

from .somepkg import func1

他里面的相对引用会变成 __main__.somepkg ,而这个显然是找不到的。如果要使其能够运行,需要将相对路径变成绝对路径

from somepkg import func1

或者把 xx.py 变成一个子模块,让上层变成主模块,比如变成如下结构

project
|- lib
   |- __init__.py
   `- xx.py
`- main.py

main.py 的代码

from lib import xx

JavaScript 内存相关

浏览器内存泄漏案例

https://github.com/zhansingsong/js-leakage-patterns

NodeJS 内存 GC (V8 GC)

分代回收

新生代中的对象主要通过 Scavenge 算法进行垃圾回收。在 Scavenge 的具体实现中,主要采用了 Cheney 算法。
晋升条件主要有两个:

  • 对象是否经历过一次 Scavenge 回收,是的话,则移动到老生代
  • To 空间已经使用超过 25%,To 空间对象移动到老生代

老生代使用了标记-清除算法 (Mark-Sweep)进行垃圾回收,并使用标记-压缩算法 (Mark-Compact)整理内存碎片,提高内存的利用率

因为上述三种方式都是「全停顿」,所以 V8 引入增量标记、惰性清理、并发标记。

Mark-Sweep

白灰黑
黑是活跃内存
灰是扫描过程中,相邻节点,等待后续处理
白是可以释放的内存

Mark-Compact

释放原理

可达性分析,GC ROOTS,深度优先遍历

写屏障

记录老生区指向新生区的情况

Accurate GC

SMI, small integer
V8 预留所有的字(word,32位机器是 4 字节,64 位机器是8字节)的最后一位用于标记(tag)这个字中的内容的类型,1 表示指针,0 表示整数,这样给定一个内存中的字,它能通过查看最后一位快速地判断它包含的指针还是整数,并且可以将整数直接存储在字中,无需先通过一个指针间接引用过来,节省空间。

Vuex 的一张图

image

其实这个图依然是 MVC 相关的知识,还是和 binding 相关。远程数据如何和本地的视图进行交互,更一般的,用户的操作如何反馈到视图上?这一层的 binding 在 Mutations 里面声明。

查看 Mutations 相关的 API,可以发现就是和浏览器 Event 一样的概念:事件 + 实现。事件是 Mutations 里函数的名称,实现是 Mutations 里函数的实现。另外,Mutations 暴露出 commit 方法,通过该方法触发相关事件,进而修改内部的 State。为了降低概念和实现的复杂度,Mutations 里面的实现要求是同步的操作,修改 State 和渲染 View 被安排在同一个操作(tick)里面,而不是被分拆成异步。

那如何进行异步更新呢?通过 Action,所有异步的逻辑在 Action 里面实现。Action 的接口中有 Mutations 暴露的 commit 方法,当动作需要修改状态时调用 commit。定义好的 Action 通过 Dispatch 进行分发。

其它的概念

  • state 视图状态的逻辑化表示,这个就是字面意思
  • getter 约等于 computed,有些值是需要计算出来,比如 full_name = first_name + last_name
  • module 用来做分层管理。在 state 比较多,并且存在层级关系的时候(比如父子组件)使用

React v17.0.2 源码分析笔记

笔记中梳理了

  • fiber 更新逻辑
    • Render Phase and Commit Phase
    • 如何合并多次 setState 以及 setState 同步/异步
    • 如何给高优先级让步以及重新恢复执行
    • 生命周期和 Ref
    • Diff 的逻辑
    • Update 和 UpdateQueue 结构
  • 调度的核心逻辑
  • Context API 实现原理
  • 合成事件和派发逻辑
  • Hooks 原理(待整理)

如果感兴趣,还可以深入了解一些 https://7kms.github.io/react-illustration-series/algorithm/bitfiled

  • 位运算
  • 深度优先遍历
  • 最小堆
  • 链表

React v17 0 2

[Git] show error: secret key not available when git sign

I met this problem because I had deleted my private key on my laptop in order to protect my secret.
I use gpg2 to solve this problem. Ref: link
git config --global gpg.program gpg2

And, I also change the signingkey to my key of sign. You can use gpg2 --edit-key [key-id] to see which key is to sign.

Good luck.

BTW. If you want to sign the previous commit, you can use git --amend --signoff

Django Rest Framework 的一些理解

今天按照自己的知识体系理解了一下 Django Rest Framework。其实这就是一个 MVVM 的实现。而 MVVM 的核心:binding

请求数据和 Model 如何绑定?通过 ViewSet。那之间的数据如何进行转换?即用户通过请求提交的数据如何转成 Django 的模型(Python 内的数据)?通过 ViewSet 里声明的 Serializer。这也就是为什么在 Serializer 里面可以声明和 Model 的绑定。

  • ViewSet 其它的比如请求方法和实现绑定(as_view,get 请求绑定 list 方法)。在需要处理一些复杂的数据操作时需要理解的概念。
  • Serializer 的 create 和 update 方法则是告诉框架如何进行数据转换(自定义的 Serializer 需要知道这些知识)

未来十年技术预言

  1. 低代码搭建式应用开发会被普及
  2. wasm 将成为全球最大的软件「分发/运行平台」
  3. 车机将成为下一个流量入口
  4. rust 将取代 c/c++

我们十年后见。哦,对了,人类将在五十年内实现可控核聚变(狗头,划掉)

生成导航

_config.yml

bucket_title:
- text: archive
  url: /archive/index.html
- text: tags
  url: /tags/index.html
- text: draft
  url: https://github.com/creamidea/creamidea.github.com/tree/master/_draft
- text: emotion
  url: http://www.douban.com/people/creamidea/notes

bucket_title.html

{% unless page.layout == 'front' %}
<nav class="bucket-sort">
  {% for title in site.bucket_title %}
  {% assign page_url = page.url|split: '/' %}
  {% if page_url[1] == title.text or page.layout == link.layout %}
  {% assign href = "#" %}
  {% assign class = "no-link" %}
  {% assign active = "active" %}
  {% else %}
  {% assign href = title.url %}
  {% assign class = "" %}
  {% assign active = "" %}
  {% endif %}
  <h5 class="bucket-title {{active}}">
    <a  href="{{ href }}" class="{{ class }}">
      <span class="bucket-header-title">{{ title.text }}</span>
    </a>
  </h5>
  {% endfor %}
</nav>
{% endunless %}

Nodejs 之 Worker 简单分析

分析过程

分析从 Nodejs Doc Worker Threads 开始,并基于 v14.18.2 源码进行分析,期间参考以下 3 篇文章

总的来说,Worker Thread 的 Thread 不是「传统」意义上的系统级 Thread,也不是 Chrome 里面的 Worker,而是自己实现了一套 Worker Thread 机制,以及 MessagePort(用于线程之间通信方式)。每一个 Worker 都有自己独立的 V8 Context(可以理解为一个独立的 Tab 标签),libuv(事件循环),Nodejs 实例(比如内部 env 等状态维持)。线程间可以通过 ArrayBuffers, SharedArrayBuffer, Atomics 等进行内存共享。详细内容可以点击此处查看。

其中线程间通信使用的是 libuv Async handle 机制,详细的原理可以参考 libuv 源码分析(七)异步唤醒(Async) 。概括来说:一般我们认为 EventLoop 「最终」会「卡」在 Polling IO,那么工作线程做完的通知如何进入 EventLoop ?底层原理是模拟 IO 事件,在工作完成之后就写入 IO,从而进入 EventLoop。

源码以 nodejs/node@f217025 为基础进行分析,可自行下载比对。分析止步于 V8 和 libuv。

主线程

源码从 lib/worker_threads.js 开始,重点跟进 Worker 类的实现。Worker 来做 internal/worker 这个内部 module,对应的文件路径 lib/internal/worker.js

定位到 Worker 类的实现,继承 EventEmitter,所以可以使用 on 相关 API

class Worker extends EventEmitter {
    constructor(filename, options = {}) {
		// ....

		// 调用内部 c++ 实现的 Worker 类
		this[kHandle] = new WorkerImpl(url,
	                                     env === process.env ? null : env,
	                                     options.execArgv,
	                                     parseResourceLimits(options.resourceLimits),
	                                     !!(options.trackUnmanagedFds ?? true));

		//....

       // 下面一行语句,就是开启一个新的线程,运行创建 Context 和执行子线程代码,创建 EventLoop 并运行 uv_run
       // Actually start the new thread now that everything is in place.
       this[kHandle].startThread();
    }
}

WorkerImpl

继续跟进 WorkerImpl 的实现,其来自于内部绑定的 worker,对应的文件路径,src/node_worker.cc

// 下面 2 句通过 Nodejs Register 机制将 worker 注册
NODE_MODULE_CONTEXT_AWARE_INTERNAL(worker, node::worker::InitWorker)
NODE_MODULE_EXTERNAL_REFERENCE(worker, node::worker::RegisterExternalReferences)

继续跟入 node::worker::InitWorker

void InitWorker(Local<Object> target,
                Local<Value> unused,
                Local<Context> context,
                void* priv) {
	  //...

	  {
	      Local<FunctionTemplate> w = env->NewFunctionTemplate(Worker::New);
	  
  	      // 省略初始化过程

		  // 在这里注册 Worker 的实现,也就是在 Nodejs 里面使用的 WorkerImpl
	      env->SetConstructorFunction(target, "Worker", w);
	  }

	 // 省略

	 // 获取通信端口
     env->SetMethod(target, "getEnvMessagePort", GetEnvMessagePort);

      // 设置主线程标志
	  target
	        ->Set(env->context(),
	              FIXED_ONE_BYTE_STRING(env->isolate(), "isMainThread"),
	              Boolean::New(env->isolate(), env->is_main_thread()))
	        .Check();

     // 省略
}

Worker::New 实现

void Worker::New(const FunctionCallbackInfo<Value>& args) {
	// 参数解析
	// ...

  Worker* worker = new Worker(env,
                              args.This(),
                              url,
                              per_isolate_opts,
                              std::move(exec_argv_out),
                              env_vars);

	// worker 初始化之后处理
}

Worker 实现,继承 AsyncWrap

Worker::Worker(Environment* env,
               Local<Object> wrap,
               const std::string& url,
               std::shared_ptr<PerIsolateOptions> per_isolate_opts,
               std::vector<std::string>&& exec_argv,
               std::shared_ptr<KVStore> env_vars)
    : AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER),
      per_isolate_opts_(per_isolate_opts),
      exec_argv_(exec_argv),
      platform_(env->isolate_data()->platform()),
      thread_id_(AllocateEnvironmentThreadId()),
      env_vars_(env_vars) {

  // Set up everything that needs to be set up in the parent environment.
  parent_port_ = MessagePort::New(env, env->context());


  // 设置 message_port_string,thread_id_string

  // 设置 argv_,对应文件名或者运行的 JS 代码。argv_ 将在 Worker::Run 中调用
  argv_ = std::vector<std::string>{env->argv()[0]};

}

startThread

改函数在 src/node_worker.cc 中被注册和实现

// 注册模块
NODE_MODULE_CONTEXT_AWARE_INTERNAL(worker, node::worker::InitWorker)

// 注册函数
env->SetProtoMethod(w, "startThread", Worker::StartThread);

// 实现 StartThread
void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {
  int ret = uv_thread_create_ex(&w->tid_, &thread_options, [](void* arg) {

    Worker* w = static_cast<Worker*>(arg);
    w->Run();

  }, static_cast<void*>(w));
}

StartThread 的实现基于 libuv 的 uv_thread_create_ex 创建新的线程,并在子线程中执行 Run 函数,其实现如下:

void Worker::Run() {
    // 调用 CreateEnvironment 创建新的环境,也就是对应 JS 执行环境实例,实现在文件 src/api/environment.cc CreateEnvironment
    // 调用 LoadEnvironment 加载并执行用户编写的子线程代码,详细过程可以参考执行 Nodejs 主实例 [#33](https://github.com/creamidea/creamidea.github.com/issues/32) 
    // 调用 SpinEventLoop 创建新的 libuv 事件循环,实现在文件 src/api/embed_helpers.cc SpinEventLoop
}

子线程

子线程按照「正常」Nodejs 运行模式运行,通过 parentPort.postMessage 方式进行通信。

和主线程执行的区别,在 src/node.cc,这里会执行 main/worker_thread.js 文件,这个会在下面提到

  if (env->worker_context() != nullptr) {
    return StartExecution(env, "internal/main/worker_thread");
  }

parentPort 赋值过程

  • lib/worker_threads.js 设置为 null
  • lib/internal/main/worker_thread.js 设置为 publicPort

综合上面的信息,也就是说子线程在运行的时候,被注入了 parentPort,于是就可以用了。

publicPort 实现在 lib/internal/main/worker_thread.js,在 message 事件的回调中出现。这个 message 事件来自 lib/internal/worker.js

this[kPort].postMessage({
      argv,
      type: messageTypes.LOAD_SCRIPT,
      filename,
      doEval,
      cwdCounter: cwdCounter || workerIo.sharedCwdCounter,
      workerData: options.workerData,
      environmentData,
      publicPort: port2,
      manifestURL: getOptionValue('--experimental-policy') ?
        require('internal/process/policy').url :
        null,
      manifestSrc: getOptionValue('--experimental-policy') ?
        require('internal/process/policy').src :
        null,
      hasStdin: !!options.stdin
    }, transferList);

后面有时间再单开一篇文章分析 Message 机制吧(逃

React18 一些笔记

简要的更新过程

以 FunctionComponent 和 一次 click 内 setState 为例

setState

省略 React 处理事件过程,直接进入 onClick 处理函数调用 setState,这里的 setState 就是 dispatchSetState 函数。

  • requestUpdateLane 根据触发事件的类型获取优先级。click 为 1,是离散事件的优先级(最开始的时候默认为 16,如果是 transition 则从 64 开始)
  • 构造 update 数据结构
    • hasEagerState 和 eagerState 是优化策略。当首次更新(首次更新判断条件:fiber.lanes === NoLane && (!fiber.alternate || fiber.alternate.lanes === NoLane),则会被设置为 true,并计算出 state 记录。如果计算的 state 和当前 state 一致,则直接退出本次更新
  • enqueueUpdate (存储到 hook.queue)
  • 调用 scheduleUpdateOnFiber 进入更新调度

scheduleUpdateOnFiber

  • markUpdateLaneFromFiberToRoot 在 sourceFiber 和 alternate 上都设置 lanes。并通过 childLanes 向上冒泡设置。最后返回 root(HostRoot)
  • markRootUpdated 也就是设置 root.pendingLanes 和 eventTimes。这里的 pendingLanes 会主要在 getNextLanes 里面使用到

if 分支处理

  • 同(root === workInProgressRoot)+渲染阶段,触发更新的处理。那么就标记 workInProgressRootRenderPhaseUpdatedLanes
  • 反之
    • root === workInProgressRoot 已经是当前更新
      • 情况一:deferRenderPhaseUpdateToNextBatch
      • 情况二:workInProgressRootExitStatus === RootSuspendedWithDelay 设置 root.suspendedLanes
    • ensureRootIsScheduled
    • 👆函数正常退出后,针对旧版的兼容处理,setTimeout 等这类内触发的更新,需要同步调用,也就是立即出发 React 渲染工作 performSyncWorkOnRoot

ensureRootIsScheduled
以 Root 为参数,安排调度 performSyncWorkOnRoot

  • markStarvedLanesAsExpired 处理是否有过期的更新(开启并发之后,防止低优先级的任务始终没有机会执行的问题)。从 pendingLanes 这个赛道里面分理出过期的任务,放到 expiredLanes 赛道内
  • getNextLanes 获取当前执行中的优先级
    • pendingLanes 是首先判断的条件。如果这个为 NoLanes,那么就直接返回 NoLanes

if 分支处理:没有获取到调度优先级

  • nextLanes === NoLanes 说明没有需要做的更新,重置相关变量,退出更新

  • getHighestPriorityLane(nextLanes) 取出最高优先级的任务作为本次更新的任务调度优先级

if 分支处理:本次更新和当前更新(如果有)比较

  • 和当前更新任务相同,直接退出更新
  • 优先级不同,并且当前存在执行中的调度任务(并发嘛,可能存在执行中的任务),则取消当前任务
  • 新建任务
    • 同步:scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root))
      • 如果支持 supportsMicrotasks,那么安排一下 flushSyncCallbacks (这里面会执行 performSyncWorkOnRoot,也就是将 React 工作安排在同一个浏览器 task 里面)
      • 不支持,通过 scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks),这就会被安排到下一个 task 立即执行
    • 并发:scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root)) 安排到下一个浏览器 task

performSyncWorkOnRoot

开始遍历 root 前的一些准备工作

  • flushPassiveEffects 如果存在 rootWithPendingPassiveEffects(这个会在 commitRoot 阶段被设置。如果存在说明上一次任务执行完,调度的 effect 还没有执行。那么就需要在本次更新任务执行前,执行完成。留给本次更新一个干净的环境),则执行

    • commitPassiveUnmountEffects
      • 处理删除节点的 destroy,方向是 parent -> child。detachFiberAfterEffects 方向是 child -> parent
      • 处理本次在更新 HookHasEffect 范围内的 destroy,方向是从 child -> parent
    • commitPassiveMountEffects
      • 处理本次在更新 HookHasEffect 范围内的 create,方向是从 child -> parent
  • getNextLanes 再次获取当前任务的优先级
    if 分支判断:优先级

  • 不包含 SyncLane,则将本次更新调用 ensureRootIsScheduled 重新安排,并退出本次更新,等重新调度

  • 包含 SyncLane,调用 renderRootSync 开始遍历

  • 👆函数执行完成有一个退出码

    • RootErrored 错误处理,重试一次
    • RootFatalErrored 错误处理,直接清空当前栈帧(prepareFreshStack),处理 Suspense 标记(markRootSuspended)。然后安排调度任务(ensureRootIsScheduled)。最后抛出异常退出
    • 设置如下状态,然后 commitRoot
  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  • commitRoot 正常退出之后,再次安排调度 ensureRootIsScheduled

React 核心工作:Render + Commit

Render 阶段

Commit 阶段

  • 重置当前任务的一些状态
  root.callbackNode = null;
  root.callbackPriority = NoLane;
  • 处理已经完成的赛道(任务)
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);
  • 其他状态
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
  • 是否有副作用,有的话安排调度 useEffect 等。👇会判断是否有同步,如果有那么就会同步执行 useEffect
if (
    (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
    (finishedWork.flags & PassiveMask) !== NoFlags
  ) {
    if (!rootDoesHavePassiveEffects) {
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      // workInProgressTransitions might be overwritten, so we want
      // to store it in pendingPassiveTransitions until they get processed
      // We need to pass this through as an argument to commitRoot
      // because workInProgressTransitions might have changed between
      // the previous render and commit if we throttle the commit
      // with setTimeout
      pendingPassiveTransitions = transitions;
      scheduleCallback(NormalSchedulerPriority, () => {
        flushPassiveEffects();
        // This render triggered passive effects: release the root cache pool
        // *after* passive effects fire to avoid freeing a cache pool that may
        // be referenced by a node in the tree (HostRoot, Cache boundary etc)
        return null;
      });
    }
  }
  • 如果有内容更新,比如 DOM 变化等。首先设置执行状态 executionContext |= CommitContext。然后是 3 大步骤:

  • commitBeforeMutationEffects

    • 焦点事件等一些处理
    • 这一阶段,FunctionComponent 没有事做。不过可以提一下 ClassComponent,会调用 getSnapshotBeforeUpdate,方向是 child -> parent
  • commitMutationEffects

    • 👇都是删除的处理,方向是 parent -> child
    • 如果 FunctionComponent 有删除的状态(parentFiber.deletions),调用 destroy 函数(HookInsertion, HookLayout),删除同时会将 fiber 从树内删除(detachFiberMutation)
    • 补充:ClassComponent safelyDetachRef 和 componentWillUnmount,方向是 parent -> child
    • 补充:HostComponent safelyDetachRef。方向是 parent -> child (commitDeletionEffectsOnFiber 里面,有一个很故意的做法,case HostComponent 时没有 break 也没有 return,于是就会进入👇 HostText 的处理)
    • 补充:HostText removeChild。方向是 parent -> child

    • commitPlacement 插入 DOM (insertOrAppendPlacementNode),遍历方向是 child -> parent。DOM 节点的插入方向 parent -> child

    • 下面是更新的处理
    • 有更新(Update)处理:commitHookEffectListUnmount(HookInsertion | HookHasEffect),commitHookEffectListMount(HookInsertion | HookHasEffect), commitHookEffectListUnmount(HookLayout | HookHasEffect) 即 useLayoutEffect 的 destroy 在这里被执行
    • 补充:ClassComponent safelyDetachRef,方向是 child -> parent
    • 补充:HostComponent safelyDetachRef,如果有 Update,commitUpdate 。方向是 child -> parent
  • commitLayoutEffects

    • commitHookEffectListMount(HookLayout | HookHasEffect) 即 useLayoutEffect 的 create 在这里被执行。方向是 child -> parent
    • 补充:ClassComponent componentDidUpdate(首次就是 componentDidMount) 和 setState 的 callback
    • 补充:HostComponent 会处理 autoFocus
  • 设置 useEffect 执行需要的状态

rootDoesHavePassiveEffects = false;
// 👇这个是遍历时需要的数据
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
  • 再次安排调度 ensureRootIsScheduled,确定没有需要更新的内容

  • 如果本次任务有同步的优先级,那么同步执行 useEffect(针对的是离散型事件,比如点击,让其同步执行)

  if (
    includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
    root.tag !== LegacyRoot
  ) {
    flushPassiveEffects();
  }
  • 处理剩余的任务 remainingLanes
  • 循环调用检查,就是那个 50 次
  • 调用 flushSyncCallbacks,确保同步任务都被执行完成(If layout work was scheduled, flush it now.)
  • 结束

此时使用 updateState,来到 updateReducer 处理函数

React fiber(HostRoot) payload{element} 的作用

element 为第一个组件,即 HostRoot 的 children

在 render 函数里面,处理成 update.payload = { element }

在 render phase updateHostRoot 函数内,通过判读前后 children,判断 HostRoot 是否可以 bailout。所以作用就是快速路径返回的作用。

function updateHostRoot() {
    ...
    const prevState = workInProgress.memoizedState;
    const prevChildren = prevState.element;
    ...
    if (nextChildren === prevChildren) {
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
    ...
}

在 processUpdateQueue 内将 update.payload = { element },计算 state 结果放到 memoizedState 里

function processUpdateQueue() {
  ...
  // getStateFromUpdate 函数内 UpdateState 分支处理
  // payload 不是函数,所以直接返回 payload
  newState = getStateFromUpdate(
    workInProgress,
    queue,
    update,
    newState,
    props,
    instance,
  );
  ...
  workInProgress.memoizedState = newState;
}

React Route 中 ErrorBoundary 死循环问题

有如下代码,如果此时一个 route 的 element 抛出异常,那么该异常会不断进入 ErrorBoundaryrender 函数进行异常渲染,直至最大调用栈报错。经过一番排出,最终解决方案,只需要对 ErrorBoundary 增加 key 属性即可,我想了解 React 运行机制的小伙伴已经嗅到真相的味道了。

<Routes>
  {routes.map((route, index) => {
    return (
      <Route
        path={route.url}
        element={
          <Suspense fallback="loading...">
            <ErrorBoundary>
              {route.element}
            </ErrorBoundary>
          </Suspense>
        }
      />
    );
  })}
</Routes>

// ErrorBoundary
export class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    console.log('send:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      if (this.state.error.code === 401) {
        return <Navigate to="/login" replace={true} />;
      }
      if (this.state.error.code === 403) {
        return <NoPermission />;
      }
      // 你可以自定义降级后的 UI 并渲染
      return <h1>{this.state.error.message || "Something went wrong"}</h1>;
    }

    return this.props.children; 
  }
}

// 其中一个 route.element
export default function Dashboard() {
  // 通过 axios 网络访问获取数据异常
  throw {
    success: false,
    code: 401,
    message: 'no permission',
  };
}

在 React 的 reconcileChildFibers 阶段,本案例是进入了 reconcileSingleElement 函数逻辑:先比较 key,再比较 elementType,问题就出在这 2 次比较。

展开分析这里之前,我们先了解一下 react router 中 RouteRoutes 这 2 个组件。

从上面得知,虽然我们代码里面使用 map 返回了多个 ErrorBoundary,但其实在 React 里面只有命中当前 URL 的组件(一个或多个,本案例只有一个)。

// 这里 Routes 只是渲染了命中的子组件
<Routes>
  {routes.map((route, index) => {
    return (
      <Route
        path={route.url}
        element={
          <Suspense fallback="loading...">
            <ErrorBoundary>
              {route.element}
            </ErrorBoundary>
          </Suspense>
        }
      />
    );
  })}
</Routes>

现在我们回到 reconcileChildFibers 的比较情况。如果没有设置 key(默认值为 null),null === null 表明 key 是一致的。那么就看 elementType,从 Routes 渲染情况刻制,elementType 也是一致的。于是进入了复用 fiber(ErrorBoundary) 逻辑。这个影响就是 this.state.hasError 始终为 true。那么就会再次进入 <Navigate to="/login" replace={true} /> 逻辑,从而表现为不断循环的现象。

Nodejs 启动分析

前言

在前一篇文章 #32 之后,对于 Nodejs 启动有了一个大致的想法。这篇文章将概括讲述 Nodejs 的 3 种主要启动方式

  • 正常启动方式(用户脚本,REPL,stdin)
  • child_process 启动方式
  • worker_thread 启动方式

源码以 https://github.com/nodejs/node/tree/f2170253b694c488f8ad2616dfc5c66b6a3c90a0 为基础进行分析,可自行下载比对。分析止步于 V8 和 libuv,仅分析在 Nodejs 层的流程。

启动方式

正常启动方式

旅程从 node.gyp 开始。(没错,整个 Nodejs 构建和写其插件的构建方式相同,都是使用 node-gyp 构建工具构建)

{
      'sources': [
        'src/node_main.cc'
      ],
}

进入 src/node_main.cc 文件

int main(int argc, char* argv[]) {
  // 省略:跨平台处理

  // Disable stdio buffering, it interacts poorly with printf()
  // calls elsewhere in the program (e.g., any logging from V8.)
  setvbuf(stdout, nullptr, _IONBF, 0);
  setvbuf(stderr, nullptr, _IONBF, 0);

  // Start 实现在 node.cc
  return node::Start(argc, argv);
}

进入 node.cc

int Start(int argc, char** argv) {
    // 省略:前置处理

  {
    // 省略:缓存处理

    // 配置 libuv,为启动 Event Loop 作准备
    uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME);

    // 创建 Node 主实例,并运行
    NodeMainInstance main_instance(&params,
                                   uv_default_loop(),
                                   per_process::v8_platform.Platform(),
                                   result.args,
                                   result.exec_args,
                                   indices);

    // Run 的实现在 node_main_instance.cc 文件
    result.exit_code = main_instance.Run(env_info);
  }

  TearDownOncePerProcess();
  return result.exit_code;
}

进入 node_main_instance.cc。在此处,终于进入核心启动逻辑,重要的是在 Run 函数中调用这 2 个函数

  • LoadEnvironment 实现在 src/api/environment.cc
  • SpinEventLoop 实现在 src/api/embed_helpers.cc

LoadEnvironment

MaybeLocal<Value> LoadEnvironment(
    Environment* env,
    StartExecutionCallback cb) {
  env->InitializeLibuv();
  env->InitializeDiagnostics();

  return StartExecution(env, cb);
}

初始化 libuv 等相关组件,最重要的是调用 StartExecution 执行用户写的脚本。当然在真正执行用户脚步之前,还有许多工作要做,下面我们简单分析一下。

StartExecution 有 2 个实现,函数接口分别如下:

  • MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb)
  • MaybeLocal<Value> StartExecution(Environment* env, const char* main_script_id)

前者负责处理不同情况下以什么入口启动运行,下文要说的 worker_thread 启动也是在这个函数内判断;后者负责找到具体入口并编译和运行。让我们把目光聚焦到主模式下启动的代码

  // 用户脚本启动方式
  if (!first_argv.empty() && first_argv != "-") {
    return StartExecution(env, "internal/main/run_main_module");
  }

  // REPL
  if (env->options()->force_repl || uv_guess_handle(STDIN_FILENO) == UV_TTY) {
    return StartExecution(env, "internal/main/repl");
  }

  // stdin
  return StartExecution(env, "internal/main/eval_stdin");

在了解了针对不同模式的分类处理之后,我们继续分析「真正」的 StartExecution 方法实现,其内部调用链关系

StartExecution -> ExecuteBootstrapper -> LookupAndCompile

ExecuteBootstrapper 内部会调用 LookupAndCompileLookupAndCompile 会根据前面不同模式下的分类,找到对应入口文件并经过 V8 处理成可调用的 Function

MaybeLocal<Value> ExecuteBootstrapper(Environment* env,
                                      const char* id,
                                      std::vector<Local<String>>* parameters,
                                      std::vector<Local<Value>>* arguments) {

  MaybeLocal<Function> maybe_fn =
      NativeModuleEnv::LookupAndCompile(env->context(), id, parameters, env);

  // 此处调用编译成功的用户代码,也就是用户代码在此时开始执行
  MaybeLocal<Value> result = fn->Call(env->context(),
                                      Undefined(env->isolate()),
                                      arguments->size(),
                                      arguments->data());

  // ...
}

下面我们简单看一下 LookupAndCompile 是如何实现。其实现在 src/node_native_module.cc 文件,调用关系

LookupAndCompile -> CompileFunctionInContext

CompileFunctionInContext 已经来到 V8 API 层面,到此已经到达笔者极限。后续如果有能力再进一步探索。

关于 Nodejs 是如何读取用户代码,并交给 V8 进行编译和执行过程就如上所示了。我们回过头看一下,以用户脚本为例,其「引导」过程是什么样子。

用户脚本的入口脚本:internal/main/run_main_module.js

require('internal/bootstrap/pre_execution');
require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);

我们稍微看一下 internal/modules/cjs/loader.js,这个是以 CJS 规范的模块加载,后续还有以 ESM 规范的加载方式(internal/process/esm_loader.js),就请读者自己查看啦:)

在 loader 文件内,会构造 exports, require, module, __filename, __dirname 包裹用户代码;还有对不同文件的加载处理,.js, .node, .json

SpinEventLoop

该部分主要是调用 libuv 函数 uv_run

Maybe<int> SpinEventLoop(Environment* env) {
  {
    do {
      // 调用 uv_run 开启 Event Loop
      uv_run(env->event_loop(), UV_RUN_DEFAULT);

      more = uv_loop_alive(env->event_loop());
      if (more && !env->is_stopping()) continue;

      if (EmitProcessBeforeExit(env).IsNothing())
        break;

      // Emit `beforeExit` if the loop became alive either after emitting
      // event, or after running some callbacks.
      more = uv_loop_alive(env->event_loop());
    } while (more == true && !env->is_stopping());
  }
}

总结

从目前的代码分析来看,V8 执行用户代码和 Event Loop 是在同一个线程内,这个也能过验证长时间运行用户代码会「饿死」Event Loop。

Event Loop 本身也只使用一个线程,Nodejs 主实例最终会「卡」在 uv_run ,直到循环结束。

child_process 启动方式

child_process 的启动方式以 spawn 为线索开始搜寻。我们从 lib/child_process.js 开始

function spawn(file, args, options) {
  // 删去细节,保留主干
  const child = new ChildProcess();
  return child;
}

ChildProcess 来自 internal/child_process.js

function ChildProcess() {
  FunctionPrototypeCall(EventEmitter, this);

  // 实例化 Process
  this._handle = new Process();
}
ObjectSetPrototypeOf(ChildProcess.prototype, EventEmitter.prototype);
ObjectSetPrototypeOf(ChildProcess, EventEmitter);


ChildProcess.prototype.spawn = function(options) {
  const err = this._handle.spawn(options);
}

Process 来自内部模块 process_wrap。该模块实现在 src/process_wrap.cc

// 注册模块
NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_wrap, node::ProcessWrap::Initialize)

// 绑定 Spawn 函数实现
env->SetProtoMethod(constructor, "spawn", Spawn);

// Spawn 函数实现
static void Spawn(const FunctionCallbackInfo<Value>& args) {
    // 调用 uv_spawn 能力,创建进程执行
    // process_ 是 uv_process_t,可以理解为从 libuv 获取结果的句柄
    // options 是 uv_process_options_s,描述了执行的文件地址,参数等创建进程必须的信息
    int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
}

可以看到,最终调用的是 libuv 的 uv_spawn 能力

worker_thread 启动方式

worker_thread 启动方式详见 #32 ,这里不再赘述。

深入阅读

Nodejs

libuv

现代 C++

V8

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.