From 5be15cc6a44937b6937a9487e1633f8a5fb25e4a Mon Sep 17 00:00:00 2001 From: Dom Date: Mon, 6 Nov 2017 09:31:49 +0000 Subject: [PATCH] Merge Development (#57) * add DropAllIndexes() method (#25) Create a new method to drop all the indexes of a collection in a single call * readme: credit @feliixx for #25 (#26) * send metadata during handshake (#28) fix [#484](https://github.com/go-mgo/mgo/issues/484) Annotate connections with metadata provided by the connecting client. informations send: { "aplication": { // optional "name": "myAppName" } "driver": { "name": "mgo", "version": "v2" }, "os": { "type": runtime.GOOS, "architecture": runtime.GOARCH } } to set "application.name", add `appname` param in options of string connection URI, for example : "mongodb://localhost:27017?appname=myAppName" * Update README to add appName (#32) * docs: elaborate on what appName does * readme: add appName to changes * add method CreateView() (#33) Fix #30. Thanks to @feliixx for the time and effort. * readme: credit @feliixx in the README (#36) * Don't panic on indexed int64 fields (#23) * Stop all db instances after tests (#462) If all tests pass, the builds for mongo earlier than 2.6 are still failing. Running a clean up fixes the issue. * fixing int64 type failing when getting indexes and trying to type them * requested changes relating to case statement and panic * Update README.md to credit @mapete94. * tests: ensure indexed int64 fields do not cause a panic in Indexes() See: * https://github.com/globalsign/mgo/pull/23 * https://github.com/go-mgo/mgo/issues/475 * https://github.com/go-mgo/mgo/pull/476 * Add collation option to collection.Create() (#37) - Allow specifying the default collation for the collection when creating it. - Add some documentation to query.Collation() method. fix #29 * Test against MongoDB 3.4.x (#35) * test against MongoDB 3.4.x * tests: use listIndexes to assert index state for 3.4+ * make test pass against v3.4.x - skip `TestViewWithCollation` because of SERVER-31049, cf: https://jira.mongodb.org/browse/SERVER-31049 - add versionAtLeast() method in init.js script to better detect server version fixes #31 * Introduce constants for BSON element types (#41) * bson.Unmarshal returns time in UTC (#42) * readme: add missing features / credit * Adds missing collation feature description (by @feliixx). * Adds missing 3.4 tests description (by @feliixx). * Adds BSON constants description (by @bozaro). * Adds UTC time.Time unmarshalling (by @gazoon). * fix golint, go vet and gofmt warnings (#44) Fixes #43 * readme: credit @feliixx (#46) * Fix GetBSON() method usage (#40) * Fix GetBSON() method usage Original issue --- You can't use type with custom GetBSON() method mixed with structure field type and structure field reference type. For example, you can't create custom GetBSON() for Bar type: ``` struct Foo { a Bar b *Bar } ``` Type implementation (`func (t Bar) GetBSON()` ) would crash on `Foo.b = nil` value encoding. Reference implementation (`func (t *Bar) GetBSON()` ) would not call on `Foo.a` value encoding. After this change --- For type implementation `func (t Bar) GetBSON()` would not call on `Foo.b = nil` value encoding. In this case `nil` value would be seariazied as `nil` BSON value. For reference implementation `func (t *Bar) GetBSON()` would call even on `Foo.a` value encoding. * Minor refactoring * readme: credit @bozaro (#47) * Improve cursorData struct unmarshaling speed (#49) This change remove full BSON decoding on: - parsing to `bson.Raw` and `bson.DocElem` fields; - skipping unused BSON fields. * readme: credit @bozaro and @idy (#53) * readme: credit @bozaro and @idy * readme: add @idy to contributor list * do not lock while writing to a socket (#52) (#54) fix #51 --- README.md | 3 + bson/bson_test.go | 13 ++ bson/decode.go | 475 +++++++++++++++++++++++++++------------------- bson/encode.go | 1 + socket.go | 5 +- 5 files changed, 297 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index 36688b3d0..87cde972e 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Further PR's (with tests) are welcome, but please maintain backwards compatibili * Consistently unmarshal time.Time values as UTC ([details](https://github.com/globalsign/mgo/pull/42)) * Enforces best practise coding guidelines ([details](https://github.com/globalsign/mgo/pull/44)) * GetBSON correctly handles structs with both fields and pointers ([details](https://github.com/globalsign/mgo/pull/40)) +* Improved bson.Raw unmarshalling performance ([details](https://github.com/globalsign/mgo/pull/49)) +* Minimise socket connection timeouts due to excessive locking ([details](https://github.com/globalsign/mgo/pull/52)) --- @@ -42,6 +44,7 @@ Further PR's (with tests) are welcome, but please maintain backwards compatibili * @eaglerayp * @feliixx * @fmpwizard +* @idy * @jameinel * @gazoon * @mapete94 diff --git a/bson/bson_test.go b/bson/bson_test.go index 695f9029d..db72d8a06 100644 --- a/bson/bson_test.go +++ b/bson/bson_test.go @@ -82,6 +82,19 @@ func testUnmarshal(c *C, data string, obj interface{}) { err := bson.Unmarshal([]byte(data), zero) c.Assert(err, IsNil) c.Assert(zero, DeepEquals, obj) + + testUnmarshalRawElements(c, []byte(data)) +} + +func testUnmarshalRawElements(c *C, data []byte) { + elems := []bson.RawDocElem{} + err := bson.Unmarshal(data, &elems) + c.Assert(err, IsNil) + for _, elem := range elems { + if elem.Value.Kind == bson.ElementDocument || elem.Value.Kind == bson.ElementArray { + testUnmarshalRawElements(c, elem.Value.Data) + } + } } type testItemType struct { diff --git a/bson/decode.go b/bson/decode.go index 3e257f846..e71eac23f 100644 --- a/bson/decode.go +++ b/bson/decode.go @@ -28,7 +28,9 @@ package bson import ( + "errors" "fmt" + "io" "math" "net/url" "reflect" @@ -56,13 +58,6 @@ func corrupted() { panic("Document is corrupted") } -func settableValueOf(i interface{}) reflect.Value { - v := reflect.ValueOf(i) - sv := reflect.New(v.Type()).Elem() - sv.Set(v) - return sv -} - // -------------------------------------------------------------------------- // Unmarshaling of documents. @@ -137,8 +132,7 @@ func (d *decoder) readDocTo(out reflect.Value) { out.Set(reflect.New(outt.Elem())) } if setter := getSetter(outt, out); setter != nil { - var raw Raw - d.readDocTo(reflect.ValueOf(&raw)) + raw := d.readRaw(ElementDocument) err := setter.SetBSON(raw) if _, ok := err.(*TypeError); err != nil && !ok { panic(err) @@ -156,7 +150,10 @@ func (d *decoder) readDocTo(out reflect.Value) { var fieldsMap map[string]fieldInfo var inlineMap reflect.Value - start := d.i + if outt == typeRaw { + out.Set(reflect.ValueOf(d.readRaw(ElementDocument))) + return + } origout := out if outk == reflect.Interface { @@ -195,22 +192,20 @@ func (d *decoder) readDocTo(out reflect.Value) { clearMap(out) } case reflect.Struct: - if outt != typeRaw { - sinfo, err := getStructInfo(out.Type()) - if err != nil { - panic(err) + sinfo, err := getStructInfo(out.Type()) + if err != nil { + panic(err) + } + fieldsMap = sinfo.FieldsMap + out.Set(sinfo.Zero) + if sinfo.InlineMap != -1 { + inlineMap = out.Field(sinfo.InlineMap) + if !inlineMap.IsNil() && inlineMap.Len() > 0 { + clearMap(inlineMap) } - fieldsMap = sinfo.FieldsMap - out.Set(sinfo.Zero) - if sinfo.InlineMap != -1 { - inlineMap = out.Field(sinfo.InlineMap) - if !inlineMap.IsNil() && inlineMap.Len() > 0 { - clearMap(inlineMap) - } - elemType = inlineMap.Type().Elem() - if elemType == typeIface { - d.docType = inlineMap.Type() - } + elemType = inlineMap.Type().Elem() + if elemType == typeIface { + d.docType = inlineMap.Type() } } case reflect.Slice: @@ -227,70 +222,58 @@ func (d *decoder) readDocTo(out reflect.Value) { panic("Unsupported document type for unmarshalling: " + out.Type().String()) } - if outt == typeRaw { - d.skipDoc() - } else { - end := int(d.readInt32()) - end += d.i - 4 - if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { + end := int(d.readInt32()) + end += d.i - 4 + if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { + corrupted() + } + for d.in[d.i] != '\x00' { + kind := d.readByte() + name := d.readCStr() + if d.i >= end { corrupted() } - for d.in[d.i] != '\x00' { - kind := d.readByte() - name := d.readCStr() - if d.i >= end { - corrupted() - } - switch outk { - case reflect.Map: - e := reflect.New(elemType).Elem() - if d.readElemTo(e, kind) { - k := reflect.ValueOf(name) - if convertKey { - k = k.Convert(keyType) - } - out.SetMapIndex(k, e) + switch outk { + case reflect.Map: + e := reflect.New(elemType).Elem() + if d.readElemTo(e, kind) { + k := reflect.ValueOf(name) + if convertKey { + k = k.Convert(keyType) } - case reflect.Struct: - if outt == typeRaw { - d.dropElem(kind) + out.SetMapIndex(k, e) + } + case reflect.Struct: + if info, ok := fieldsMap[name]; ok { + if info.Inline == nil { + d.readElemTo(out.Field(info.Num), kind) } else { - if info, ok := fieldsMap[name]; ok { - if info.Inline == nil { - d.readElemTo(out.Field(info.Num), kind) - } else { - d.readElemTo(out.FieldByIndex(info.Inline), kind) - } - } else if inlineMap.IsValid() { - if inlineMap.IsNil() { - inlineMap.Set(reflect.MakeMap(inlineMap.Type())) - } - e := reflect.New(elemType).Elem() - if d.readElemTo(e, kind) { - inlineMap.SetMapIndex(reflect.ValueOf(name), e) - } - } else { - d.dropElem(kind) - } + d.readElemTo(out.FieldByIndex(info.Inline), kind) } - case reflect.Slice: - } - - if d.i >= end { - corrupted() + } else if inlineMap.IsValid() { + if inlineMap.IsNil() { + inlineMap.Set(reflect.MakeMap(inlineMap.Type())) + } + e := reflect.New(elemType).Elem() + if d.readElemTo(e, kind) { + inlineMap.SetMapIndex(reflect.ValueOf(name), e) + } + } else { + d.dropElem(kind) } + case reflect.Slice: } - d.i++ // '\x00' - if d.i != end { + + if d.i >= end { corrupted() } } - d.docType = docType - - if outt == typeRaw { - out.Set(reflect.ValueOf(Raw{0x03, d.in[start:d.i]})) + d.i++ // '\x00' + if d.i != end { + corrupted() } + d.docType = docType } func (d *decoder) readArrayDocTo(out reflect.Value) { @@ -332,9 +315,12 @@ func (d *decoder) readSliceDoc(t reflect.Type) interface{} { tmp := make([]reflect.Value, 0, 8) elemType := t.Elem() if elemType == typeRawDocElem { - d.dropElem(0x04) + d.dropElem(ElementArray) return reflect.Zero(t).Interface() } + if elemType == typeRaw { + return d.readSliceOfRaw() + } end := int(d.readInt32()) end += d.i - 4 @@ -371,6 +357,151 @@ func (d *decoder) readSliceDoc(t reflect.Type) interface{} { return slice.Interface() } +func BSONElementSize(kind byte, offset int, buffer []byte) (int, error) { + switch kind { + case ElementFloat64: // Float64 + return 8, nil + case ElementJavaScriptWithoutScope: // JavaScript without scope + fallthrough + case ElementSymbol: // Symbol + fallthrough + case ElementString: // UTF-8 string + size, err := getSize(offset, buffer) + if err != nil { + return 0, err + } + if size < 1 { + return 0, errors.New("String size can't be less then one byte") + } + size += 4 + if offset+size > len(buffer) { + return 0, io.ErrUnexpectedEOF + } + if buffer[offset+size-1] != 0 { + return 0, errors.New("Invalid string: non zero-terminated") + } + return size, nil + case ElementArray: // Array + fallthrough + case ElementDocument: // Document + size, err := getSize(offset, buffer) + if err != nil { + return 0, err + } + if size < 5 { + return 0, errors.New("Declared document size is too small") + } + return size, nil + case ElementBinary: // Binary + size, err := getSize(offset, buffer) + if err != nil { + return 0, err + } + if size < 0 { + return 0, errors.New("Binary data size can't be negative") + } + return size + 5, nil + case Element06: // Undefined (obsolete, but still seen in the wild) + return 0, nil + case ElementObjectId: // ObjectId + return 12, nil + case ElementBool: // Bool + return 1, nil + case ElementDatetime: // Timestamp + return 8, nil + case ElementNil: // Nil + return 0, nil + case ElementRegEx: // RegEx + end := offset + for i := 0; i < 2; i++ { + for end < len(buffer) && buffer[end] != '\x00' { + end++ + } + end++ + } + if end > len(buffer) { + return 0, io.ErrUnexpectedEOF + } + return end - offset, nil + case ElementDBPointer: // DBPointer + size, err := getSize(offset, buffer) + if err != nil { + return 0, err + } + if size < 1 { + return 0, errors.New("String size can't be less then one byte") + } + return size + 12 + 4, nil + case ElementJavaScriptWithScope: // JavaScript with scope + size, err := getSize(offset, buffer) + if err != nil { + return 0, err + } + if size < 4+5+5 { + return 0, errors.New("Declared document element is too small") + } + return size, nil + case ElementInt32: // Int32 + return 4, nil + case ElementTimestamp: // Mongo-specific timestamp + return 8, nil + case ElementInt64: // Int64 + return 8, nil + case ElementDecimal128: // Decimal128 + return 16, nil + case ElementMaxKey: // Max key + return 0, nil + case ElementMinKey: // Min key + return 0, nil + default: + return 0, errors.New(fmt.Sprintf("Unknown element kind (0x%02X)", kind)) + } +} + +func (d *decoder) readRaw(kind byte) Raw { + size, err := BSONElementSize(kind, d.i, d.in) + if err != nil { + corrupted() + } + if d.i+size > len(d.in) { + corrupted() + } + d.i += size + return Raw{ + Kind: kind, + Data: d.in[d.i-size : d.i], + } +} + +func (d *decoder) readSliceOfRaw() interface{} { + tmp := make([]Raw, 0, 8) + end := int(d.readInt32()) + end += d.i - 4 + if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { + corrupted() + } + for d.in[d.i] != '\x00' { + kind := d.readByte() + for d.i < end && d.in[d.i] != '\x00' { + d.i++ + } + if d.i >= end { + corrupted() + } + d.i++ + e := d.readRaw(kind) + tmp = append(tmp, e) + if d.i >= end { + corrupted() + } + } + d.i++ // '\x00' + if d.i != end { + corrupted() + } + return tmp +} + var typeSlice = reflect.TypeOf([]interface{}{}) var typeIface = typeSlice.Elem() @@ -396,11 +527,8 @@ func (d *decoder) readRawDocElems(typ reflect.Type) reflect.Value { d.docType = typ slice := make([]RawDocElem, 0, 8) d.readDocWith(func(kind byte, name string) { - e := RawDocElem{Name: name} - v := reflect.ValueOf(&e.Value) - if d.readElemTo(v.Elem(), kind) { - slice = append(slice, e) - } + e := RawDocElem{Name: name, Value: d.readRaw(kind)} + slice = append(slice, e) }) slicev := reflect.New(typ).Elem() slicev.Set(reflect.ValueOf(slice)) @@ -433,76 +561,35 @@ func (d *decoder) readDocWith(f func(kind byte, name string)) { // -------------------------------------------------------------------------- // Unmarshaling of individual elements within a document. - -var blackHole = settableValueOf(struct{}{}) - func (d *decoder) dropElem(kind byte) { - switch kind { - case 0x01, 0x09, 0x11, 0x12: // double, utc datetime, timestamp, int64 - d.i += 8 - case 0x02, 0x0D, 0x0E: // string, javascript, symbol - l := int(d.readInt32()) - if l <= 0 || d.i+l >= len(d.in) || d.in[d.i+l-1] != 0x00 { - corrupted() - } - d.i += l - case 0x03, 0x04: // doc, array - d.skipDoc() - case 0x05: // binary - l := int(d.readInt32()) - k := d.readByte() - if k == 0x02 && l > 4 { - rl := int(d.readInt32()) - if rl != l-4 { - corrupted() - } - } - d.i += l - case 0x06: // undefined - case 0x07: // objectId - d.i += 12 - case 0x08: - k := d.readByte() - if k != 0x00 && k != 0x01 { - corrupted() - } - case 0x0A: // null - case 0x0B: // regex - d.readCStr() - d.readCStr() - case 0x0C: // dbpointer - d.dropElem(0x02) - d.i += 12 - case 0x0F: - start := d.i - l := int(d.readInt32()) - d.dropElem(0x02) // string - d.skipDoc() - if d.i != start+l { - corrupted() - } - case 0x10: // int32 - d.i += 4 - case 0x13: // decimal - d.i += 16 - case 0xFF, 0x7F: //min key, max key - default: - d.readElemTo(blackHole, kind) + size, err := BSONElementSize(kind, d.i, d.in) + if err != nil { + corrupted() } - - if d.i > len(d.in) { + if d.i+size > len(d.in) { corrupted() } + d.i += size } // Attempt to decode an element from the document and put it into out. // If the types are not compatible, the returned ok value will be // false and out will be unchanged. func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { + outt := out.Type() - start := d.i + if outt == typeRaw { + out.Set(reflect.ValueOf(d.readRaw(kind))) + return true + } - if kind == 0x03 { + if outt == typeRawPtr { + raw := d.readRaw(kind) + out.Set(reflect.ValueOf(&raw)) + return true + } + + if kind == ElementDocument { // Delegate unmarshaling of documents. outt := out.Type() outk := out.Kind() @@ -522,24 +609,39 @@ func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { case typeRawDocElem: out.Set(d.readRawDocElems(outt)) default: - d.skipDoc() + d.dropElem(kind) } return true } - d.skipDoc() + d.dropElem(kind) return true } + if setter := getSetter(outt, out); setter != nil { + err := setter.SetBSON(d.readRaw(kind)) + if err == ErrSetZero { + out.Set(reflect.Zero(outt)) + return true + } + if err == nil { + return true + } + if _, ok := err.(*TypeError); !ok { + panic(err) + } + return false + } + var in interface{} switch kind { - case 0x01: // Float64 + case ElementFloat64: in = d.readFloat64() - case 0x02: // UTF-8 string + case ElementString: in = d.readStr() - case 0x03: // Document + case ElementDocument: panic("Can't happen. Handled above.") - case 0x04: // Array + case ElementArray: outt := out.Type() if setterStyle(outt) != setterNone { // Skip the value so its data is handed to the setter below. @@ -558,20 +660,20 @@ func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { default: in = d.readSliceDoc(typeSlice) } - case 0x05: // Binary + case ElementBinary: b := d.readBinary() - if b.Kind == 0x00 || b.Kind == 0x02 { + if b.Kind == BinaryGeneric || b.Kind == BinaryBinaryOld { in = b.Data } else { in = b } - case 0x06: // Undefined (obsolete, but still seen in the wild) + case Element06: // Undefined (obsolete, but still seen in the wild) in = Undefined - case 0x07: // ObjectId + case ElementObjectId: in = ObjectId(d.readBytes(12)) - case 0x08: // Bool + case ElementBool: in = d.readBool() - case 0x09: // Timestamp + case ElementDatetime: // Timestamp // MongoDB handles timestamps as milliseconds. i := d.readInt64() if i == -62135596800000 { @@ -579,17 +681,17 @@ func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { } else { in = time.Unix(i/1e3, i%1e3*1e6).UTC() } - case 0x0A: // Nil + case ElementNil: in = nil - case 0x0B: // RegEx + case ElementRegEx: in = d.readRegEx() - case 0x0C: + case ElementDBPointer: in = DBPointer{Namespace: d.readStr(), Id: ObjectId(d.readBytes(12))} - case 0x0D: // JavaScript without scope + case ElementJavaScriptWithoutScope: in = JavaScript{Code: d.readStr()} - case 0x0E: // Symbol + case ElementSymbol: in = Symbol(d.readStr()) - case 0x0F: // JavaScript with scope + case ElementJavaScriptWithScope: start := d.i l := int(d.readInt32()) js := JavaScript{d.readStr(), make(M)} @@ -598,52 +700,30 @@ func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { corrupted() } in = js - case 0x10: // Int32 + case ElementInt32: in = int(d.readInt32()) - case 0x11: // Mongo-specific timestamp + case ElementTimestamp: // Mongo-specific timestamp in = MongoTimestamp(d.readInt64()) - case 0x12: // Int64 + case ElementInt64: switch out.Type() { case typeTimeDuration: in = time.Duration(time.Duration(d.readInt64()) * time.Millisecond) default: in = d.readInt64() } - case 0x13: // Decimal128 + case ElementDecimal128: in = Decimal128{ l: uint64(d.readInt64()), h: uint64(d.readInt64()), } - case 0x7F: // Max key + case ElementMaxKey: in = MaxKey - case 0xFF: // Min key + case ElementMinKey: in = MinKey default: panic(fmt.Sprintf("Unknown element kind (0x%02X)", kind)) } - outt := out.Type() - - if outt == typeRaw { - out.Set(reflect.ValueOf(Raw{kind, d.in[start:d.i]})) - return true - } - - if setter := getSetter(outt, out); setter != nil { - err := setter.SetBSON(Raw{kind, d.in[start:d.i]}) - if err == ErrSetZero { - out.Set(reflect.Zero(outt)) - return true - } - if err == nil { - return true - } - if _, ok := err.(*TypeError); !ok { - panic(err) - } - return false - } - if in == nil { out.Set(reflect.Zero(outt)) return true @@ -818,15 +898,6 @@ func (d *decoder) readElemTo(out reflect.Value, kind byte) (good bool) { // -------------------------------------------------------------------------- // Parsers of basic types. -func (d *decoder) skipDoc() { - end := int(d.readInt32()) - end += d.i - 4 - if end <= d.i || end > len(d.in) || d.in[end-1] != '\x00' { - corrupted() - } - d.i = end -} - func (d *decoder) readRegEx() RegEx { re := RegEx{} re.Pattern = d.readCStr() @@ -838,7 +909,7 @@ func (d *decoder) readBinary() Binary { l := d.readInt32() b := Binary{} b.Kind = d.readByte() - if b.Kind == 0x02 && l > 4 { + if b.Kind == BinaryBinaryOld && l > 4 { // Weird obsolete format with redundant length. rl := d.readInt32() if rl != l-4 { @@ -898,6 +969,16 @@ func (d *decoder) readInt32() int32 { (uint32(b[3]) << 24)) } +func getSize(offset int, b []byte) (int, error) { + if offset+4 > len(b) { + return 0, io.ErrUnexpectedEOF + } + return int((uint32(b[offset]) << 0) | + (uint32(b[offset+1]) << 8) | + (uint32(b[offset+2]) << 16) | + (uint32(b[offset+3]) << 24)), nil +} + func (d *decoder) readInt64() int64 { b := d.readBytes(8) return int64((uint64(b[0]) << 0) | diff --git a/bson/encode.go b/bson/encode.go index 61f388fa1..f307c31ec 100644 --- a/bson/encode.go +++ b/bson/encode.go @@ -52,6 +52,7 @@ var ( typeDocElem = reflect.TypeOf(DocElem{}) typeRawDocElem = reflect.TypeOf(RawDocElem{}) typeRaw = reflect.TypeOf(Raw{}) + typeRawPtr = reflect.PtrTo(reflect.TypeOf(Raw{})) typeURL = reflect.TypeOf(url.URL{}) typeTime = reflect.TypeOf(time.Time{}) typeString = reflect.TypeOf("") diff --git a/socket.go b/socket.go index 72fab9cf7..f6158189c 100644 --- a/socket.go +++ b/socket.go @@ -549,16 +549,15 @@ func (socket *mongoSocket) Query(ops ...interface{}) (err error) { socket.replyFuncs[requestId] = request.replyFunc requestId++ } - + socket.Unlock() debugf("Socket %p to %s: sending %d op(s) (%d bytes)", socket, socket.addr, len(ops), len(buf)) - stats.sentOps(len(ops)) + stats.sentOps(len(ops)) socket.updateDeadline(writeDeadline) _, err = socket.conn.Write(buf) if !wasWaiting && requestCount > 0 { socket.updateDeadline(readDeadline) } - socket.Unlock() return err }