macOS Sierra (10.12) のsqlite3をmultiprocessingと一緒に使うときの注意

# 条件

* macOS Sierra (10.2, 10.2.1)
* Sierraのsqlite3を実質fork()している先(multiprocessing)から使っているときに発生する

追記(2016-10-25): 10.2.1 がリリースされましたが、本件について状況は改善していないようです。
追記(2017-03-31): 10.12.4 で確認しました。修正されていたようです。やったね！

# 検証用コード

```py3:sqlite3_sierra_exp.py
#!/usr/bin/env python

"""\
An example that crashes on macOS Sierra.
"""

from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import

from logging import getLogger, StreamHandler, Formatter
from logging import DEBUG
from multiprocessing import Process, freeze_support
from threading import Thread
import os
import sqlite3
import sys
import time

_DB_PATH = os.path.join(os.path.dirname(__file__), 'state.db')


def _connect(db_path):
    conn = sqlite3.connect(db_path)
    return conn


def get_count(db_path,  name):
    conn = _connect(db_path)
    c = conn.cursor()
    c.execute('''\
    SELECT count FROM test_table
    WHERE name = ?
    ''', (name,))
    (count,) = c.fetchone()
    conn.commit()
    conn.close()
    return count


def incrementer_process(db_path, name):
    conn = _connect(db_path)
    c = conn.cursor()
    c.execute('''\
    SELECT count FROM test_table
    WHERE name = ?
    ''', (name,))
    (count,) = c.fetchone()
    c.execute('''\
    INSERT OR REPLACE INTO test_table
    (name, count)
    VALUES (?, ?)
    ''', (name, count + 1))
    conn.commit()
    conn.close()


def main():
    logger = getLogger(__name__)
    handler = StreamHandler()
    logger.addHandler(handler)
    logger.setLevel(DEBUG)
    handler.setLevel(DEBUG)
    handler.setFormatter(Formatter('%(asctime)s %(message)s'))
    logger.info('python version: {}'.format(sys.version))
    logger.info('module_version: {}'.format(sqlite3.version))
    logger.info('sqlite3_version: {}'.format(sqlite3.sqlite_version))
    name = 'name'
    db_path = _DB_PATH
    conn = _connect(db_path)
    c = conn.cursor()
    c.execute('''\
    DROP TABLE IF EXISTS
    test_table
    ''')
    c.execute('''\
    CREATE TABLE IF NOT EXISTS
    test_table (name text, count integer)
    ''')
    c.execute('''\
    CREATE UNIQUE INDEX IF NOT EXISTS
    files_index ON test_table (name)
    ''')
    c.execute('''\
    INSERT OR REPLACE INTO test_table
    (name, count)
    VALUES (?, ?)
    ''', (name, 0))
    conn.commit()
    conn.close()
    logger.debug('Creating a child')
    child = Process(target=incrementer_process,
                    args=(db_path, name))
    #child = Thread(target=incrementer_process,
    #           args=process_args,
    #           kwargs=process_kwargs)
    child.start()
    child.join()
    logger.info('value: {}'.format(get_count(db_path, name)))


if __name__ == '__main__':
    freeze_support()
    main()
```

メインのプロセスでsqlite3のDBを作成し、`multiprocessing.Process`経由で値を1インクリメントします。
レースコンディション等が発生するような状況ではございません。
やや蛇足ですが、`therading.Thread`では問題は発生しません。

# 動作結果

```shell-session
$ pyenv shell 3.5.2-nativesqlite3
$ ./sqlite3_sierra_exp.py
2016-10-24 22:14:29,065 python version: 3.5.2 (default, Oct 24 2016, 22:06:56)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)]
2016-10-24 22:14:29,065 module_version: 2.6.0
2016-10-24 22:14:29,065 sqlite3_version: 3.14.0
2016-10-24 22:14:29,072 Creating a child
2016-10-24 22:14:29,081 value: 0
```

pyenv使ってるとは言え、ちょっとにわかに信じられない結果が出てきました。0だと？？

ここでSierra内蔵のPythonを使うと……

```shell-sessino
$ ./sqlite3_sierra_exp.py
2016-10-24 22:15:05,994 python version: 2.7.10 (default, Jul 30 2016, 18:31:42)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)]
2016-10-24 22:15:05,994 module_version: 2.6.0
2016-10-24 22:15:05,994 sqlite3_version: 3.14.0
2016-10-24 22:15:06,001 Creating a child
2016-10-24 22:15:06,009 value: 0
```

<img width="560" alt="スクリーンショット 2016-10-24 22.15.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/14365/422a0ac5-b7bf-64e0-73cf-36894844584c.png">

ひぇっ！？

比較のためDebian Jessie上での実行結果と比較します

```shell-session
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 8.6 (jessie)
Release:	8.6
Codename:	jessie
$ pyenv shell system
$ ./sqlite3_sierra_exp.py
2016-10-24 22:16:21,316 python version: 2.7.9 (default, Jun 29 2016, 13:08:31)
[GCC 4.9.2]
2016-10-24 22:16:21,316 module_version: 2.6.0
2016-10-24 22:16:21,316 sqlite3_version: 3.8.7.1
2016-10-24 22:16:21,352 Creating a child
2016-10-24 22:16:21,368 value: 1
```

で、ですよね。1ですよね……

Windowsでも試してみます。コピペ面倒なのでスクリーンショットですが……

<img width="499" alt="スクリーンショット 2016-10-24 22.22.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/14365/10eb1556-9f56-cfd4-a9b9-353f35721b6a.png">

1ですよねぇ……(嘆息

# 原因

結論を申し上げると、どうやら以下のバグ (正確にmacOS付属のlibsqlite3の問題) を踏み抜いた結果のようです。
[Apple-supplied libsqlite3 on OS X is not fork safe;  can cause crashes](http://bugs.python.org/issue27126)

# 対策

OS付属のライブラリの修正を待つ、が一つ。あるいはHomebrewからsqlite3 3.15.0 を持ってきてそちらを使うとこの症状自体はなくなります。
逆に言えばこの部分を変えないと、どのPythonバージョンでも上の問題に足を取られる可能性があります。

```shell-session
$ brew info sqlite3
sqlite: stable 3.15.0 (bottled) [keg-only]
Command-line interface for SQLite
https://sqlite.org/
/usr/local/Cellar/sqlite/3.9.2 (10 files, 2.8M)
  Poured from bottle on 2016-08-03 at 14:21:08
/usr/local/Cellar/sqlite/3.15.0 (11 files, 2.9M)
  Poured from bottle on 2016-10-24 at 18:12:13
From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/sqlite.rb
==> Dependencies
Recommended: readline ✔
Optional: icu4c ✔
==> Options
--universal
	Build a universal binary
--with-dbstat
	Enable the 'dbstat' virtual table
--with-docs
	Install HTML documentation
--with-fts
	Enable the FTS3 module
--with-fts5
	Enable the FTS5 module (experimental)
--with-functions
	Enable more math and string functions for SQL queries
--with-icu4c
	Enable the ICU module
--with-json1
	Enable the JSON1 extension
--with-secure-delete
	Defaults secure_delete to on
--with-session
	Enable the session extension
--with-unlock-notify
	Enable the unlock notification feature
--without-readline
	Build without readline support
--without-rtree
	Disable the R*Tree index module
==> Caveats
This formula is keg-only, which means it was not symlinked into /usr/local.

macOS provides an older sqlite3.

Generally there are no consequences of this for you. If you build your
own software and it requires this formula, you'll need to add to your
build variables:

    LDFLAGS:  -L/usr/local/opt/sqlite/lib
    CPPFLAGS: -I/usr/local/opt/sqlite/include
    PKG_CONFIG_PATH: /usr/local/opt/sqlite/lib/pkgconfig

$ LDFLAGS="-L/usr/local/opt/sqlite/lib" \
  CPPFLAGS="-I/usr/local/opt/sqlite/include" \
  PKG_CONFIG_PATH="/usr/local/opt/sqlite/lib/pkgconfig" \
  pyenv install 3.5.2
(中略)
$ pyenv shell 3.5.2
$ ./sqlite3_sierra_exp.py
2016-10-24 22:19:30,973 python version: 3.5.2 (default, Oct 24 2016, 21:47:12)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)]
2016-10-24 22:19:30,973 module_version: 2.6.0
2016-10-24 22:19:30,973 sqlite3_version: 3.15.0
2016-10-24 22:19:30,978 Creating a child
2016-10-24 22:19:30,992 value: 1
```

sqlite3_version の部分が変わっているのがポイント。

# おまけ

pyenvのPython環境下だとクラッシュしたことを示すポップアップが出てきません。`except BaseException`ですら捕捉できないレベルでProcessがクラッシュするため、クラッシュ自体に気づかないという事態に。

うげぇつかれた