@cryptotaxi247 / kubo / commits / 5474e15e9

blockstore locks return unlocker object now

License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>

Jeromy committed Jan 15, 2016 at 15:14 UTC 5474e15e93e0103b887b09c0c4c96b2c99c5862e
16 files changed +228 -126
blocks/blockstore/blockstore.go
+19 -6
@@ -43,13 +43,13 @@ type GCBlockstore interface {
43 // GCLock locks the blockstore for garbage collection. No operations
44 // that expect to finish with a pin should ocurr simultaneously.
45 // Reading during GC is safe, and requires no lock.
46 - GCLock() func()
46 + GCLock() Unlocker
47
48 // PinLock locks the blockstore for sequences of puts expected to finish
49 // with a pin (before GC). Multiple put->pin sequences can write through
50 // at the same time, but no GC should not happen simulatenously.
51 // Reading during Pinning is safe, and requires no lock.
52 - PinLock() func()
52 + PinLock() Unlocker
53
54 // GcRequested returns true if GCLock has been called and is waiting to
55 // take the lock
@@ -198,16 +198,29 @@ func (bs *blockstore) AllKeysChan(ctx context.Context) (<-chan key.Key, error) {
198 return output, nil
199 }
200
201 -func (bs *blockstore) GCLock() func() {
201 +type Unlocker interface {
202 + Unlock()
203 +}
204 +
205 +type unlocker struct {
206 + unlock func()
207 +}
208 +
209 +func (u *unlocker) Unlock() {
210 + u.unlock()
211 + u.unlock = nil // ensure its not called twice
212 +}
213 +
214 +func (bs *blockstore) GCLock() Unlocker {
215 atomic.AddInt32(&bs.gcreq, 1)
216 bs.lk.Lock()
217 atomic.AddInt32(&bs.gcreq, -1)
205 - return bs.lk.Unlock
218 + return &unlocker{bs.lk.Unlock}
219 }
220
208 -func (bs *blockstore) PinLock() func() {
221 +func (bs *blockstore) PinLock() Unlocker {
222 bs.lk.RLock()
210 - return bs.lk.RUnlock
223 + return &unlocker{bs.lk.RUnlock}
224 }
225
226 func (bs *blockstore) GCRequested() bool {
blocks/blockstore/write_cache.go
+2 -2
@@ -59,11 +59,11 @@ func (w *writecache) AllKeysChan(ctx context.Context) (<-chan key.Key, error) {
59 return w.blockstore.AllKeysChan(ctx)
60 }
61
62 -func (w *writecache) GCLock() func() {
62 +func (w *writecache) GCLock() Unlocker {
63 return w.blockstore.(GCBlockstore).GCLock()
64 }
65
66 -func (w *writecache) PinLock() func() {
66 +func (w *writecache) PinLock() Unlocker {
67 return w.blockstore.(GCBlockstore).PinLock()
68 }
69
core/commands/files/files.go
+17 -15
@@ -109,8 +109,7 @@ func statNode(ds dag.DAGService, fsn mfs.FSNode) (*Object, error) {
109 return nil, err
110 }
111
112 - // add to dagserv to ensure its available
113 - k, err := ds.Add(nd)
112 + k, err := nd.Key()
113 if err != nil {
114 return nil, err
115 }
@@ -159,6 +158,11 @@ var FilesCpCmd = &cmds.Command{
158 return
159 }
160
161 + flush, found, _ := req.Option("flush").Bool()
162 + if !found {
163 + flush = true
164 + }
165 +
166 src, err := checkPath(req.Arguments()[0])
167 if err != nil {
168 res.SetError(err, cmds.ErrNormal)
@@ -181,6 +185,14 @@ var FilesCpCmd = &cmds.Command{
185 res.SetError(err, cmds.ErrNormal)
186 return
187 }
188 +
189 + if flush {
190 + err := mfs.FlushPath(node.FilesRoot, dst)
191 + if err != nil {
192 + res.SetError(err, cmds.ErrNormal)
193 + return
194 + }
195 + }
196 },
197 }
198
@@ -501,8 +513,8 @@ Warning:
513
514 create, _, _ := req.Option("create").Bool()
515 trunc, _, _ := req.Option("truncate").Bool()
504 - flush, set, _ := req.Option("flush").Bool()
505 - if !set {
516 + flush, fset, _ := req.Option("flush").Bool()
517 + if !fset {
518 flush = true
519 }
520
@@ -529,14 +541,7 @@ Warning:
541 }
542
543 if flush {
532 - defer func() {
533 - fi.Close()
534 - err := mfs.FlushPath(nd.FilesRoot, path)
535 - if err != nil {
536 - res.SetError(err, cmds.ErrNormal)
537 - return
538 - }
539 - }()
544 + defer fi.Close()
545 } else {
546 defer fi.Sync()
547 }
@@ -653,9 +658,6 @@ are run with the '--flush=false'.
658 return
659 }
660
656 - // take the lock and defer the unlock
657 - defer nd.Blockstore.PinLock()()
658 -
661 path := "/"
662 if len(req.Arguments()) > 0 {
663 path = req.Arguments()[0]
core/commands/pin.go
+1 -2
@@ -54,8 +54,7 @@ on disk.
54 return
55 }
56
57 - unlock := n.Blockstore.PinLock()
58 - defer unlock()
57 + defer n.Blockstore.PinLock().Unlock()
58
59 // set recursive flag
60 recursive, found, err := req.Option("recursive").Bool()
core/core.go
-17
@@ -474,23 +474,6 @@ func (n *IpfsNode) loadBootstrapPeers() ([]peer.PeerInfo, error) {
474 func (n *IpfsNode) loadFilesRoot() error {
475 dsk := ds.NewKey("/local/filesroot")
476 pf := func(ctx context.Context, k key.Key) error {
477 - ds := n.Repo.Datastore()
478 - if old, err := ds.Get(dsk); err == nil && old != nil {
479 - _ = n.Pinning.Unpin(ctx, key.Key(old.([]byte)), true)
480 - }
481 - nnd, err := n.DAG.Get(ctx, k)
482 - if err != nil {
483 - return err
484 - }
485 -
486 - if err := n.Pinning.Pin(ctx, nnd, true); err != nil {
487 - return err
488 - }
489 -
490 - if err := n.Pinning.Flush(); err != nil {
491 - return err
492 - }
493 -
477 return n.Repo.Datastore().Put(dsk, []byte(k))
478 }
479
core/coreunix/add.go
+11 -11
@@ -20,6 +20,7 @@ import (
20 "github.com/ipfs/go-ipfs/pin"
21 context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context"
22
23 + bs "github.com/ipfs/go-ipfs/blocks/blockstore"
24 "github.com/ipfs/go-ipfs/commands/files"
25 core "github.com/ipfs/go-ipfs/core"
26 dag "github.com/ipfs/go-ipfs/merkledag"
@@ -100,7 +101,7 @@ type Adder struct {
101 Chunker string
102 root *dag.Node
103 mr *mfs.Root
103 - unlock func()
104 + unlocker bs.Unlocker
105 tempRoot key.Key
106 }
107
@@ -225,8 +226,7 @@ func (adder *Adder) outputDirs(path string, nd *dag.Node) error {
226 // Add builds a merkledag from the a reader, pinning all objects to the local
227 // datastore. Returns a key representing the root node.
228 func Add(n *core.IpfsNode, r io.Reader) (string, error) {
228 - unlock := n.Blockstore.PinLock()
229 - defer unlock()
229 + defer n.Blockstore.PinLock().Unlock()
230
231 fileAdder, err := NewAdder(n.Context(), n, nil)
232 if err != nil {
@@ -247,8 +247,7 @@ func Add(n *core.IpfsNode, r io.Reader) (string, error) {
247
248 // AddR recursively adds files in |path|.
249 func AddR(n *core.IpfsNode, root string) (key string, err error) {
250 - unlock := n.Blockstore.PinLock()
251 - defer unlock()
250 + n.Blockstore.PinLock().Unlock()
251
252 stat, err := os.Lstat(root)
253 if err != nil {
@@ -296,8 +295,7 @@ func AddWrapped(n *core.IpfsNode, r io.Reader, filename string) (string, *dag.No
295 }
296 fileAdder.Wrap = true
297
299 - unlock := n.Blockstore.PinLock()
300 - defer unlock()
298 + defer n.Blockstore.PinLock().Unlock()
299
300 err = fileAdder.addFile(file)
301 if err != nil {
@@ -347,8 +345,10 @@ func (adder *Adder) addNode(node *dag.Node, path string) error {
345
346 // Add the given file while respecting the adder.
347 func (adder *Adder) AddFile(file files.File) error {
350 - adder.unlock = adder.node.Blockstore.PinLock()
351 - defer adder.unlock()
348 + adder.unlocker = adder.node.Blockstore.PinLock()
349 + defer func() {
350 + adder.unlocker.Unlock()
351 + }()
352
353 return adder.addFile(file)
354 }
@@ -434,8 +434,8 @@ func (adder *Adder) maybePauseForGC() error {
434 return err
435 }
436
437 - adder.unlock()
438 - adder.unlock = adder.node.Blockstore.PinLock()
437 + adder.unlocker.Unlock()
438 + adder.unlocker = adder.node.Blockstore.PinLock()
439 }
440 return nil
441 }
mfs/dir.go
+20 -16
@@ -51,17 +51,20 @@ func NewDirectory(ctx context.Context, name string, node *dag.Node, parent child
51
52 // closeChild updates the child by the given name to the dag node 'nd'
53 // and changes its own dag node
54 -func (d *Directory) closeChild(name string, nd *dag.Node) error {
55 - mynd, err := d.closeChildUpdate(name, nd)
54 +func (d *Directory) closeChild(name string, nd *dag.Node, sync bool) error {
55 + mynd, err := d.closeChildUpdate(name, nd, sync)
56 if err != nil {
57 return err
58 }
59
60 - return d.parent.closeChild(d.name, mynd)
60 + if sync {
61 + return d.parent.closeChild(d.name, mynd, true)
62 + }
63 + return nil
64 }
65
66 // closeChildUpdate is the portion of closeChild that needs to be locked around
64 -func (d *Directory) closeChildUpdate(name string, nd *dag.Node) (*dag.Node, error) {
67 +func (d *Directory) closeChildUpdate(name string, nd *dag.Node, sync bool) (*dag.Node, error) {
68 d.lock.Lock()
69 defer d.lock.Unlock()
70
@@ -70,7 +73,10 @@ func (d *Directory) closeChildUpdate(name string, nd *dag.Node) (*dag.Node, erro
73 return nil, err
74 }
75
73 - return d.flushCurrentNode()
76 + if sync {
77 + return d.flushCurrentNode()
78 + }
79 + return nil, nil
80 }
81
82 func (d *Directory) flushCurrentNode() (*dag.Node, error) {
@@ -295,12 +301,15 @@ func (d *Directory) Unlink(name string) error {
301 }
302
303 func (d *Directory) Flush() error {
304 + d.lock.Lock()
305 nd, err := d.flushCurrentNode()
306 if err != nil {
307 + d.lock.Unlock()
308 return err
309 }
310 + d.lock.Unlock()
311
303 - return d.parent.closeChild(d.name, nd)
312 + return d.parent.closeChild(d.name, nd, true)
313 }
314
315 // AddChild adds the node 'nd' under this directory giving it the name 'name'
@@ -335,11 +344,6 @@ func (d *Directory) sync() error {
344 return err
345 }
346
338 - _, err = d.dserv.Add(nd)
339 - if err != nil {
340 - return err
341 - }
342 -
347 err = d.updateChild(name, nd)
348 if err != nil {
349 return err
@@ -352,11 +356,6 @@ func (d *Directory) sync() error {
356 return err
357 }
358
355 - _, err = d.dserv.Add(nd)
356 - if err != nil {
357 - return err
358 - }
359 -
359 err = d.updateChild(name, nd)
360 if err != nil {
361 return err
@@ -385,6 +384,11 @@ func (d *Directory) GetNode() (*dag.Node, error) {
384 return nil, err
385 }
386
387 + _, err = d.dserv.Add(d.node)
388 + if err != nil {
389 + return nil, err
390 + }
391 +
392 return d.node.Copy(), nil
393 }
394
mfs/file.go
+6 -8
@@ -73,7 +73,7 @@ func (fi *File) Close() error {
73
74 // explicitly stay locked for flushUp call,
75 // it will manage the lock for us
76 - return fi.flushUp()
76 + return fi.flushUp(true)
77 }
78 fi.Unlock()
79
@@ -82,7 +82,7 @@ func (fi *File) Close() error {
82
83 // flushUp syncs the file and adds it to the dagservice
84 // it *must* be called with the File's lock taken
85 -func (fi *File) flushUp() error {
85 +func (fi *File) flushUp(fullsync bool) error {
86 nd, err := fi.mod.GetNode()
87 if err != nil {
88 fi.Unlock()
@@ -95,20 +95,18 @@ func (fi *File) flushUp() error {
95 return err
96 }
97
98 - //name := fi.name
99 - //parent := fi.parent
98 + name := fi.name
99 + parent := fi.parent
100
101 // explicit unlock *only* before closeChild call
102 fi.Unlock()
103 - return nil
104 - //return parent.closeChild(name, nd)
103 + return parent.closeChild(name, nd, fullsync)
104 }
105
106 // Sync flushes the changes in the file to disk
107 func (fi *File) Sync() error {
108 fi.Lock()
110 - defer fi.Unlock()
111 - return fi.mod.Sync()
109 + return fi.flushUp(false)
110 }
111
112 // Seek implements io.Seeker
mfs/mfs_test.go
+69 -14
@@ -576,15 +576,15 @@ func actorRemoveFile(d *Directory) error {
576 return d.Unlink(re.Name)
577 }
578
579 -func actorReadFile(d *Directory) error {
579 +func randomFile(d *Directory) (*File, error) {
580 d, err := randomWalk(d, rand.Intn(6))
581 if err != nil {
582 - return err
582 + return nil, err
583 }
584
585 ents, err := d.List()
586 if err != nil {
587 - return err
587 + return nil, err
588 }
589
590 var files []string
@@ -595,18 +595,61 @@ func actorReadFile(d *Directory) error {
595 }
596
597 if len(files) == 0 {
598 - return nil
598 + return nil, nil
599 }
600
601 fname := files[rand.Intn(len(files))]
602 fsn, err := d.Child(fname)
603 if err != nil {
604 - return err
604 + return nil, err
605 }
606
607 fi, ok := fsn.(*File)
608 if !ok {
609 - return errors.New("file wasnt a file, race?")
609 + return nil, errors.New("file wasnt a file, race?")
610 + }
611 +
612 + return fi, nil
613 +}
614 +
615 +func actorWriteFile(d *Directory) error {
616 + fi, err := randomFile(d)
617 + if err != nil {
618 + return err
619 + }
620 + if fi == nil {
621 + return nil
622 + }
623 +
624 + size := rand.Intn(1024)
625 + buf := make([]byte, size)
626 + randbo.New().Read(buf)
627 +
628 + s, err := fi.Size()
629 + if err != nil {
630 + return err
631 + }
632 +
633 + offset := rand.Int63n(s)
634 +
635 + n, err := fi.WriteAt(buf, offset)
636 + if err != nil {
637 + return err
638 + }
639 + if n != size {
640 + return fmt.Errorf("didnt write enough")
641 + }
642 +
643 + return fi.Close()
644 +}
645 +
646 +func actorReadFile(d *Directory) error {
647 + fi, err := randomFile(d)
648 + if err != nil {
649 + return err
650 + }
651 + if fi == nil {
652 + return nil
653 }
654
655 _, err = fi.Size()
@@ -637,12 +680,7 @@ func testActor(rt *Root, iterations int, errs chan error) {
680 return
681 }
682 case 3:
640 - continue
641 - // randomly deleting things
642 - // doesnt really give us any sort of useful test results.
643 - // you will never have this in a real environment where
644 - // you expect anything productive to happen...
645 - if err := actorRemoveFile(d); err != nil {
683 + if err := actorWriteFile(d); err != nil {
684 errs <- err
685 return
686 }
@@ -698,6 +736,9 @@ func TestFlushing(t *testing.T) {
736 if err := e.AddChild("TEST", nd1); err != nil {
737 t.Fatal(err)
738 }
739 + if err := dir.AddChild("FILE", nd1); err != nil {
740 + t.Fatal(err)
741 + }
742
743 if err := FlushPath(rt, "/a/b/c/TEST"); err != nil {
744 t.Fatal(err)
@@ -711,17 +752,31 @@ func TestFlushing(t *testing.T) {
752 t.Fatal(err)
753 }
754
755 + if err := FlushPath(rt, "/FILE"); err != nil {
756 + t.Fatal(err)
757 + }
758 +
759 rnd, err := dir.GetNode()
760 if err != nil {
761 t.Fatal(err)
762 }
763
764 + fsnode, err := ft.FSNodeFromBytes(rnd.Data)
765 + if err != nil {
766 + t.Fatal(err)
767 + }
768 +
769 + if fsnode.Type != ft.TDirectory {
770 + t.Fatal("root wasnt a directory")
771 + }
772 +
773 rnk, err := rnd.Key()
774 if err != nil {
775 t.Fatal(err)
776 }
777
724 - if rnk.B58String() != "QmWcvrHUFk7LQRrA4WqKjqy7ZyRGFLVagtgNxbEodTEzQ4" {
725 - t.Fatal("dag looks wrong")
778 + exp := "QmWMVyhTuyxUrXX3ynz171jq76yY3PktfY9Bxiph7b9ikr"
779 + if rnk.B58String() != exp {
780 + t.Fatalf("dag looks wrong, expected %s, but got %s", exp, rnk.B58String())
781 }
782 }
mfs/ops.go
+21 -17
@@ -230,11 +230,6 @@ func flushPathRec(d *Directory, parts []string) (*dag.Node, error) {
230 return nil, err
231 }
232
233 - _, err = d.dserv.Add(nd)
234 - if err != nil {
235 - return nil, err
236 - }
237 -
233 return nd, nil
234 }
235
@@ -247,6 +242,7 @@ func flushPathRec(d *Directory, parts []string) (*dag.Node, error) {
242 return nil, err
243 }
244
245 + var ndagnode *dag.Node
246 switch next := next.(type) {
247 case *Directory:
248 nd, err := flushPathRec(next, parts[1:])
@@ -254,25 +250,33 @@ func flushPathRec(d *Directory, parts []string) (*dag.Node, error) {
250 return nil, err
251 }
252
257 - newnode, err := d.node.UpdateNodeLink(parts[0], nd)
258 - if err != nil {
259 - return nil, err
260 - }
261 -
262 - _, err = d.dserv.Add(newnode)
263 - if err != nil {
264 - return nil, err
265 - }
253 + ndagnode = nd
254
267 - d.node = newnode
268 - return newnode, nil
255 case *File:
256 if len(parts) > 1 {
257 return nil, fmt.Errorf("%s is a file, not a directory", parts[0])
258 }
259
274 - return next.GetNode()
260 + child, err := next.GetNode()
261 + if err != nil {
262 + return nil, err
263 + }
264 +
265 + ndagnode = child
266 default:
267 return nil, fmt.Errorf("unrecognized FSNode type: %#v", next)
268 }
269 +
270 + newnode, err := d.node.UpdateNodeLink(parts[0], ndagnode)
271 + if err != nil {
272 + return nil, err
273 + }
274 +
275 + _, err = d.dserv.Add(newnode)
276 + if err != nil {
277 + return nil, err
278 + }
279 +
280 + d.node = newnode
281 + return newnode, nil
282 }
mfs/system.go
+4 -4
@@ -29,7 +29,7 @@ var log = logging.Logger("mfs")
29 var ErrIsDirectory = errors.New("error: is a directory")
30
31 type childCloser interface {
32 - closeChild(string, *dag.Node) error
32 + closeChild(string, *dag.Node, bool) error
33 }
34
35 type NodeType int
@@ -115,7 +115,7 @@ func (kr *Root) Flush() error {
115 return err
116 }
117
118 - k, err := kr.dserv.Add(nd)
118 + k, err := nd.Key()
119 if err != nil {
120 return err
121 }
@@ -128,7 +128,7 @@ func (kr *Root) Flush() error {
128
129 // closeChild implements the childCloser interface, and signals to the publisher that
130 // there are changes ready to be published
131 -func (kr *Root) closeChild(name string, nd *dag.Node) error {
131 +func (kr *Root) closeChild(name string, nd *dag.Node, sync bool) error {
132 k, err := kr.dserv.Add(nd)
133 if err != nil {
134 return err
@@ -146,7 +146,7 @@ func (kr *Root) Close() error {
146 return err
147 }
148
149 - k, err := kr.dserv.Add(nd)
149 + k, err := nd.Key()
150 if err != nil {
151 return err
152 }
pin/gc/gc.go
+2 -2
@@ -23,7 +23,7 @@ var log = logging.Logger("gc")
23 // The routine then iterates over every block in the blockstore and
24 // deletes any block that is not found in the marked set.
25 func GC(ctx context.Context, bs bstore.GCBlockstore, pn pin.Pinner) (<-chan key.Key, error) {
26 - unlock := bs.GCLock()
26 + unlocker := bs.GCLock()
27
28 bsrv := bserv.New(bs, offline.Exchange(bs))
29 ds := dag.NewDAGService(bsrv)
@@ -41,7 +41,7 @@ func GC(ctx context.Context, bs bstore.GCBlockstore, pn pin.Pinner) (<-chan key.
41 output := make(chan key.Key)
42 go func() {
43 defer close(output)
44 - defer unlock()
44 + defer unlocker.Unlock()
45 for {
46 select {
47 case k, ok := <-keychan:
pin/pin.go
+3 -1
@@ -123,6 +123,8 @@ func (p *pinner) Pin(ctx context.Context, node *mdag.Node, recurse bool) error {
123 return nil
124 }
125
126 +var ErrNotPinned = fmt.Errorf("not pinned")
127 +
128 // Unpin a given key
129 func (p *pinner) Unpin(ctx context.Context, k key.Key, recursive bool) error {
130 p.lock.Lock()
@@ -132,7 +134,7 @@ func (p *pinner) Unpin(ctx context.Context, k key.Key, recursive bool) error {
134 return err
135 }
136 if !pinned {
135 - return fmt.Errorf("%s is not pinned", k)
137 + return ErrNotPinned
138 }
139 switch reason {
140 case "recursive":
test/sharness/t0250-files-api.sh
-5
@@ -340,11 +340,6 @@ test_files_api() {
340 ipfs files flush /
341 '
342
343 - test_expect_success "root hash is pinned after flush" '
344 - ipfs pin ls > pins &&
345 - grep $EXP_ROOT_HASH pins || (cat pins && exit 1)
346 - '
347 -
343 # test mv
344 test_expect_success "can mv dir" '
345 ipfs files mv /cats/this/is /cats/
test/sharness/t0251-files-flushing.sh new
+53
@@ -0,0 +1,53 @@
1 +#!/bin/sh
2 +#
3 +# Copyright (c) 2016 Jeromy Johnson
4 +# MIT Licensed; see the LICENSE file in this repository.
5 +#
6 +
7 +test_description="test the unix files api flushing"
8 +
9 +. lib/test-lib.sh
10 +
11 +test_init_ipfs
12 +
13 +verify_path_exists() {
14 + # simply running ls on a file should be a good 'check'
15 + ipfs files ls $1
16 +}
17 +
18 +verify_dir_contents() {
19 + dir=$1
20 + shift
21 + rm -f expected
22 + touch expected
23 + for e in $@
24 + do
25 + echo $e >> expected
26 + done
27 +
28 + test_expect_success "can list dir" '
29 + ipfs files ls $dir > output
30 + '
31 +
32 + test_expect_success "dir entries look good" '
33 + test_sort_cmp output expected
34 + '
35 +}
36 +
37 +test_launch_ipfs_daemon
38 +
39 +test_expect_success "can copy a file in" '
40 + HASH=$(echo "foo" | ipfs add -q) &&
41 + ipfs files cp /ipfs/$HASH /file
42 +'
43 +
44 +test_kill_ipfs_daemon
45 +test_launch_ipfs_daemon
46 +
47 +test_expect_success "file is still there" '
48 + verify_path_exists /file
49 +'
50 +
51 +test_kill_ipfs_daemon
52 +
53 +test_done
unixfs/mod/dagmodifier.go
-6
@@ -169,12 +169,6 @@ func (dm *DagModifier) Sync() error {
169 // Number of bytes we're going to write
170 buflen := dm.wrBuf.Len()
171
172 - // Grab key for unpinning after mod operation
173 - _, err := dm.curNode.Key()
174 - if err != nil {
175 - return err
176 - }
177 -
172 // overwrite existing dag nodes
173 thisk, done, err := dm.modifyDag(dm.curNode, dm.writeStart, dm.wrBuf)
174 if err != nil {