2017/11/11


これまでの Go の archive/zip には問題があり、zip ファイルに格納されるファイルのタイムゾーンが UTC になっていた為、日本であれば9時間ずれてしまうという問題があった。

これは zip の NTFS/UNIX/ExtendedTS extra fields を扱っていなかったのが問題。以前一度僕のパッチがマージされたが問題がありリバートされてしまっていた。

archive/zip: add FileHeader.Modified field - golang/go@6e8894d - GitHub

The ModifiedTime and ModifiedDate fields are not expressive enough for many of the time extensions t...

https://github.com/golang/go/commit/6e8894d5ffca9acc635e0d7298167122ed52ce55

この修正により、Reader は NTFS/UNIX/ExtendedTS extra fields を見る様になったので、圧縮した際のファイルのタイムスタンプが正しく扱えるようになった。Modified という属性が FileHeader に追加された。Reader は時刻拡張がある場合には ModTime と Modified が同じ物になる。Writer については Modified に自分のタイムゾーンの時刻を設定する必要がある。

header.Modified = header.Modified.In(time.Local)

もう1点、archive/zip には日本語のファイル名が正しく扱えないという問題があった。

archive/zip: add FileHeader.NonUTF8 field - golang/go@4fcc835 - GitHub

The NonUTF8 field provides users with a way to explictly tell the ZIP writer to avoid setting the UT...

https://github.com/golang/go/commit/4fcc835971ad63cf913ebe074ef6191e35a44ab9

以前僕が Golang の文字列は UTF-8 である事から archive/zip のヘッダに UTF-8 ビット(11)を立てる修正を入れたけど、このビットを立てるかどうかを決定できる NonUTF8 というフラグが追加された。この修正により、Reader は FileHeader の NonUTF8 フラグを参照し、ファイル名が UTF-8 かそうでないかを判断できる。zip のフォーマットにはファイル名が何で書かれているかは書かれていないので、そこはさすがに解凍する方が Shift_JIS 等に変換する必要がある。また Writer もこのフラグを立て、ファイル名を Shift_JIS に変換して書く事で Shift_JIS しか対応していない zip アーカイバでも読む事の出来る zip ファイルを作れる様になった。これでずいぶん長いあいだ解決してこなかった Go の archive/zip の問題は一通り修正された事になる。めでたい。

Posted at by



2017/11/08


追記

CreationFlags に直接指定出来ますね...

package main

import (
    "log"
    "os/exec"
    "syscall"
)

const (
    _IDLE_PRIORITY_CLASS = 0x40
)

func main() {
    cmd := exec.Command("notepad")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: syscall.CREATE_UNICODE_ENVIRONMENT | _IDLE_PRIORITY_CLASS,
    }
    err := cmd.Start()
    if err != nil {
        log.Fatal(err)
    }
    cmd.Wait()
}

追記ここまで

先日 GoCon に参加した際、牛乳を吹いてるアイコンの人に「Golang で優先度を変えてプロセスを起動するにはどうしたらいいんでしょうね」というお題を貰ったので書いてみました。

syscall には必要な API しか持ってないので API を持ってくるところが若干めんどくさいですが、出来る事は出来ます。プロセスをサスペンド状態で起動しておき、優先度を変更後にリジュームします。(起動後いきなり激しい動きをするヤバい奴がいない事もない)

package main

import (
    "log"
    "os/exec"
    "syscall"
    "unsafe"
)

const (
    _CREATE_SUSPENDED        = 0x00000004
    _IDLE_PRIORITY_CLASS     = 0x40
    _PROCESS_SET_INFORMATION = 0x0200
)

var (
    kernel32             = syscall.NewLazyDLL("kernel32")
    procSetPriorityClass = kernel32.NewProc("SetPriorityClass")
    procOpenThread       = kernel32.NewProc("OpenThread")
    procResumeThread     = kernel32.NewProc("ResumeThread")
    procThread32First    = kernel32.NewProc("Thread32First")
    procThread32Next     = kernel32.NewProc("Thread32Next")
)

func resumeChildThread(childpid interror {
    snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPTHREAD, 0)
    if err != nil {
        return err
    }
    defer syscall.CloseHandle(snapshot)

    const _THREAD_SUSPEND_RESUME = 0x0002

    type ThreadEntry32 struct {
        Size           uint32
        tUsage         uint32
        ThreadID       uint32
        OwnerProcessID uint32
        BasePri        int32
        DeltaPri       int32
        Flags          uint32
    }

    var te ThreadEntry32
    te.Size = uint32(unsafe.Sizeof(te))
    ret, _, err := procThread32First.Call(uintptr(snapshot), uintptr(unsafe.Pointer(&te)))
    if ret == 0 {
        return err
    }
    for te.OwnerProcessID != uint32(childpid) {
        ret, _, err = procThread32Next.Call(uintptr(snapshot), uintptr(unsafe.Pointer(&te)))
        if ret == 0 {
            return err
        }
    }
    h, _, err := procOpenThread.Call(_THREAD_SUSPEND_RESUME, 1uintptr(te.ThreadID))
    if h == 0 {
        return err
    }
    defer syscall.Close(syscall.Handle(h))

    ret, _, err = procResumeThread.Call(h)
    if ret == 0xffffffff {
        return err
    }
    return nil
}

func main() {
    cmd := exec.Command("notepad")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: syscall.CREATE_UNICODE_ENVIRONMENT | _CREATE_SUSPENDED,
    }
    err := cmd.Start()
    if err != nil {
        log.Fatal(err)
    }

    hProcess, err := syscall.OpenProcess(_PROCESS_SET_INFORMATION, trueuint32(cmd.Process.Pid))
    if err != nil {
        log.Fatal(err)
    }
    r1, _, err := procSetPriorityClass.Call(uintptr(hProcess), uintptr(_IDLE_PRIORITY_CLASS))
    if r1 == 0 {
        log.Fatal(err)
    }
    syscall.CloseHandle(hProcess)

    resumeChildThread(cmd.Process.Pid)
    cmd.Wait()
}
Posted at by



2017/10/26


printf デバッグは便利だ。技術の後退と言われようと printf でないと解決できない事はまだまだたくさんあります。

今日は net/http でクライアントが得たレスポンスの JSON を確認したいといった場合に、どうデバッグしたらいいかを書いてみたいと思う。

Go のインタフェースは大よそ io.Reader もしくは io.Writer を使う様に設計されている。こうする事でプログラムがメモリを一度に沢山確保してしまわない様にしています。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
)

type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}

func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    var foo Foo
    err = json.NewDecoder(resp.Body).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(foo.Content)
}

例えばこういうコードの、resp.Body に何が流れているのか確認したい場合、デバッグ出力する為に一旦 ioutil.ReadAll で全て読み取ったりしていないでしょうか。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}

func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(string(b))

    var foo Foo
    err = json.NewDecoder(bytes.NewReader(b)).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(foo.Content)
}

デバッグ表示したいだけなのに、ちょっとコードが増えてしまった感じがしますよね。デバッグを無効にしたいときに消すコードも多い。しかも json.NewDecoder の部分にも手を入れてしまわないといけなくてなんだか嫌な感じもします。元のコードは json.NewDecoder の箇所に手を入れられるから良いですが、時には io.Reader を引数に持つ関数に渡す必要があったり、ioutil.ReadAll で全て読み取る事が出来ないストリームデータの場合には使えません。こういった場合は io.TeeReader を使います。

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)

type Foo struct {
    ID  string `json:"id"`
    Content string `json:"content"`
}

func main() {
    resp, err := http.Get("http://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    var r io.Reader = resp.Body
    r = io.TeeReader(r, os.Stderr)

    var foo Foo
    err = json.NewDecoder(r).Decode(&foo)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(foo.Content)
}

こうしておき、必要に応じて r = io.TeeReader(r, os.Stderr) の行をコメントアウトすれば良いのです。コメントアウトを外せばデバッグ表示になります。メモリも節約出来てお得感ありますね。

Posted at by