Compare commits

..
Author SHA1 Message Date
Jakob Borg 24e691925c Add Debian build stuff 2015-05-11 22:09:38 +02:00
616 changed files with 15593 additions and 49355 deletions
+4 -5
View File
@@ -1,6 +1,4 @@
syncthing ./syncthing
!gui/syncthing
!Godeps/_workspace/src/github.com/syncthing
syncthing.exe syncthing.exe
*.tar.gz *.tar.gz
*.zip *.zip
@@ -11,6 +9,7 @@ files/pidx
bin bin
perfstats*.csv perfstats*.csv
coverage.xml coverage.xml
syncthing.sig !gui/scripts/syncthing
syncthing.md5
syncthing.exe.md5
RELEASE RELEASE
deb
+3 -19
View File
@@ -1,42 +1,31 @@
# This is the official list of Syncthing authors for copyright purposes. # This is the official list of Syncthing authors for copyright purposes.
Aaron Bieber <qbit@deftly.net> Aaron Bieber <qbit@deftly.net>
Adam Piggott <aD@simplypeachy.co.uk> <simplypeachy@users.noreply.github.com>
Alexander Graf <register-github@alex-graf.de> Alexander Graf <register-github@alex-graf.de>
Andrew Dunham <andrew@du.nham.ca> Andrew Dunham <andrew@du.nham.ca>
Antony Male <antony.male@gmail.com>
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
Audrius Butkevicius <audrius.butkevicius@gmail.com> Audrius Butkevicius <audrius.butkevicius@gmail.com>
Bart De Vries <devriesb@gmail.com> Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
Ben Curthoys <ben@bencurthoys.com> Ben Curthoys <ben@bencurthoys.com>
Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com> Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com>
Ben Sidhom <bsidhom@gmail.com> Ben Sidhom <bsidhom@gmail.com>
Brandon Philips <brandon@ifup.org> Brandon Philips <brandon@ifup.org>
Brendan Long <self@brendanlong.com> Brendan Long <self@brendanlong.com>
Brian R. Becker <brbecker@gmail.com>
Caleb Callaway <enlightened.despot@gmail.com> Caleb Callaway <enlightened.despot@gmail.com>
Carsten Hagemann <moter8@gmail.com> Carsten Hagemann <moter8@gmail.com>
Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
Chris Howie <me@chrishowie.com>
Chris Joel <chris@scriptolo.gy> Chris Joel <chris@scriptolo.gy>
Colin Kennedy <moshen.colin@gmail.com> Colin Kennedy <moshen.colin@gmail.com>
Daniel Bergmann <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
Daniel Martí <mvdan@mvdan.cc> Daniel Martí <mvdan@mvdan.cc>
Denis A. <denisva@gmail.com>
Dennis Wilson <dw@risu.io> Dennis Wilson <dw@risu.io>
Dominik Heidler <dominik@heidler.eu> Dominik Heidler <dominik@heidler.eu>
Elias Jarlebring <jarlebring@gmail.com> Elias Jarlebring <jarlebring@gmail.com>
Emil Hessman <emil@hessman.se> Emil Hessman <emil@hessman.se>
Erik Meitner <e.meitner@willystreet.coop>
Federico Castagnini <federico.castagnini@gmail.com> Federico Castagnini <federico.castagnini@gmail.com>
Felix Ableitner <me@nutomic.com> Felix Ableitner <me@nutomic.com>
Felix Unterpaintner <bigbear2nd@gmail.com> Felix Unterpaintner <bigbear2nd@gmail.com>
Francois-Xavier Gsell <fxgsell@gmail.com> Francois-Xavier Gsell <fxgsell@gmail.com>
Frank Isemann <frank@isemann.name>
Gilli Sigurdsson <gilli@vx.is> Gilli Sigurdsson <gilli@vx.is>
Jacek Szafarkiewicz <szafar@linux.pl>
Jakob Borg <jakob@nym.se> Jakob Borg <jakob@nym.se>
Jake Peterson <jake@acogdev.com>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com> James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
Jaroslav Malec <dzardacz@gmail.com> Jaroslav Malec <dzardacz@gmail.com>
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de> Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
@@ -46,11 +35,9 @@ Karol Różycki <rozycki.karol@gmail.com>
Ken'ichi Kamada <kamada@nanohz.org> Ken'ichi Kamada <kamada@nanohz.org>
Lode Hoste <zillode@zillode.be> Lode Hoste <zillode@zillode.be>
Lord Landon Agahnim <lordlandon@gmail.com> Lord Landon Agahnim <lordlandon@gmail.com>
Marcin Dziadus <dziadus.marcin@gmail.com>
Marc Laporte <marc@marclaporte.com> <marc@laporte.name> Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
Marc Pujol <kilburn@la3.org> Marc Pujol <kilburn@la3.org>
Marcin Dziadus <dziadus.marcin@gmail.com>
Mateusz Naściszewski <matin1111@wp.pl>
Matt Burke <mburke@amplify.com> <burkemw3@gmail.com>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com> Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Tilli <pyfisch@gmail.com> Michael Tilli <pyfisch@gmail.com>
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com> Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
@@ -60,13 +47,10 @@ Phill Luby <phill.luby@newredo.com>
Piotr Bejda <piotrb10@gmail.com> Piotr Bejda <piotrb10@gmail.com>
Ryan Sullivan <kayoticsully@gmail.com> Ryan Sullivan <kayoticsully@gmail.com>
Sergey Mishin <ralder@yandex.ru> Sergey Mishin <ralder@yandex.ru>
Stefan Tatschner <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org> Stefan Tatschner <stefan@sevenbyte.org>
Stefan Kuntz <stefan.github@gmail.com> <Stefan.github@gmail.com>
Tim Abell <tim@timwise.co.uk> Tim Abell <tim@timwise.co.uk>
Tobias Nygren <tnn@nygren.pp.se> Tobias Nygren <tnn@nygren.pp.se>
Tomas Cerveny <kozec@kozec.com> Tomas Cerveny <kozec@kozec.com>
Tully Robinson <tully@tojr.org> Tully Robinson <tully@tojr.org>
Tyler Brazier <tyler@tylerbrazier.com>
Veeti Paananen <veeti.paananen@rojekti.fi> Veeti Paananen <veeti.paananen@rojekti.fi>
Vil Brekin <vilbrekin@gmail.com> Vil Brekin <vilbrekin@gmail.com>
Yannic A. <eipiminusone+github@gmail.com> <eipiminus1@users.noreply.github.com>
+95 -7
View File
@@ -32,15 +32,64 @@ latest info on Transifex.
## Contributing Code ## Contributing Code
Every contribution is welcome. If you want to contribute but are unsure Every contribution is welcome. If you want to contribute but are unsure
where to start, any open issues are fair game! See the [Contribution where to start, any open issues are fair game! Be prepared for a
Guidelines](http://docs.syncthing.net/dev/contributing.html) for the full [certain amount of review](https://github.com/syncthing/syncthing/wiki/FAQ#why-are-you-being-so-hard-on-my-pull-request);
story on committing code. it's all in the name of quality. :) Following the points below will make this
a smoother process.
## Contributing Documentation Individuals making significant and valuable contributions are given
commit-access to the project. If you make a significant contribution and
are not considered for commit-access, please contact any of the
Syncthing core team members.
Updates to the [documentation site](http://docs.syncthing.net/) can be All nontrivial contributions should go through the pull request
made as pull requests on the [documentation mechanism for internal review. Determining what is "nontrivial" is left
repository](https://github.com/syncthing/docs). at the discretion of the contributor.
### Authorship
All code authors are listed in the AUTHORS file. Commits must be made
with the same name and email as listed in the AUTHORS file. To
accomplish this, ensure that your git configuration is set correctly
prior to making your first commit;
$ git config --global user.name "Jane Doe"
$ git config --global user.email janedoe@example.com
You must be reachable on the given email address. If you do not wish to
use your real name for whatever reason, using a nickname or pseudonym is
perfectly acceptable.
### Core Team
The Syncthing core team currently consists of the following members;
- Jakob Borg (@calmh)
- Audrius Butkevicius (@AudriusButkevicius)
## Coding Style
- Follow the conventions laid out in [Effective Go](https://golang.org/doc/effective_go.html)
as much as makes sense.
- All text files use Unix line endings.
- Each commit should be `go fmt` clean.
- The commit message subject should be a single short sentence
describing the change, starting with a capital letter.
- Commits that resolve an existing issue must include the issue number
as `(fixes #123)` at the end of the commit message subject.
- Imports are grouped per `goimports` standard; that is, standard
library first, then third party libraries after a blank line.
- A contribution solving a single issue or introducing a single new
feature should probably be a single commit based on the current
`master` branch. You may be asked to "rebase" or "squash" your pull
request to make sure this is the case, especially if there have been
amendments during review.
## Licensing ## Licensing
@@ -50,3 +99,42 @@ strings which are licensed under the Creative Commons Attribution 4.0
International License. You retain the copyright to code you have International License. You retain the copyright to code you have
written. written.
When accepting your first contribution, the maintainer of the project
will ensure that you are added to the AUTHORS file. You are welcome to
add yourself as a separate commit in your first pull request.
## Building
[See the documentation](https://github.com/syncthing/syncthing/wiki/Building)
on how to get started with a build environment.
## Branches
- `master` is the main branch containing good code that will end up in
the next release. You should base your work on it. It won't ever be
rebased or force-pushed to.
- `vx.y` branches exist to make patch releases on otherwise obsolete
minor releases. Should only contain fixes cherry picked from master.
Don't base any work on them.
- Other branches are probably topic branches and may be subject to
rebasing. Don't base any work on them unless you specifically know
otherwise.
## Tags
All releases are tagged semver style as `vx.y.z`. Release tags are
signed by GPG key BCE524C7.
## Tests
Yes please!
## Documentation
[Over here!](https://github.com/syncthing/syncthing/wiki)
## License
MPLv2
+21 -30
View File
@@ -1,17 +1,17 @@
{ {
"ImportPath": "github.com/syncthing/syncthing", "ImportPath": "github.com/syncthing/syncthing",
"GoVersion": "go1.5.1", "GoVersion": "go1.4",
"Packages": [ "Packages": [
"./cmd/..." "./cmd/..."
], ],
"Deps": [ "Deps": [
{ {
"ImportPath": "github.com/bkaradzic/go-lz4", "ImportPath": "github.com/bkaradzic/go-lz4",
"Rev": "74ddf82598bc4745b965729e9c6a463bedd33049" "Rev": "93a831dcee242be64a9cc9803dda84af25932de7"
}, },
{ {
"ImportPath": "github.com/calmh/du", "ImportPath": "github.com/calmh/logger",
"Rev": "3c0690cca16228b97741327b1b6781397afbdb24" "Rev": "4d4e2801954c5581e4c2a80a3d3beb3b3645fd04"
}, },
{ {
"ImportPath": "github.com/calmh/luhn", "ImportPath": "github.com/calmh/luhn",
@@ -19,28 +19,31 @@
}, },
{ {
"ImportPath": "github.com/calmh/xdr", "ImportPath": "github.com/calmh/xdr",
"Rev": "47c0042d09a827b81ee62497f99e5e0c7f0bd31c" "Rev": "5f7208e86762911861c94f1849eddbfc0a60cbf0"
},
{
"ImportPath": "github.com/golang/snappy",
"Rev": "723cc1e459b8eea2dea4583200fd60757d40097a"
}, },
{ {
"ImportPath": "github.com/juju/ratelimit", "ImportPath": "github.com/juju/ratelimit",
"Rev": "772f5c38e468398c4511514f4f6aa9a4185bc0a0" "Rev": "c5abe513796336ee2869745bff0638508450e9c5"
}, },
{ {
"ImportPath": "github.com/kardianos/osext", "ImportPath": "github.com/kardianos/osext",
"Rev": "6e7f843663477789fac7c02def0d0909e969b4e5" "Rev": "efacde03154693404c65e7aa7d461ac9014acd0c"
},
{
"ImportPath": "github.com/syncthing/protocol",
"Rev": "e7db2648034fb71b051902a02bc25d4468ed492e"
}, },
{ {
"ImportPath": "github.com/syndtr/goleveldb/leveldb", "ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "1a9d62f03ea92815b46fcaab357cfd4df264b1a0" "Rev": "87e4e645d80ae9c537e8f2dee52b28036a5dd75e"
},
{
"ImportPath": "github.com/syndtr/gosnappy/snappy",
"Rev": "156a073208e131d7d2e212cb749feae7c339e846"
}, },
{ {
"ImportPath": "github.com/thejerf/suture", "ImportPath": "github.com/thejerf/suture",
"Comment": "v1.0.1", "Rev": "ff19fb384c3fe30f42717967eaa69da91e5f317c"
"Rev": "99c1f2d613756768fc4299acd9dc621e11ed3fd7"
}, },
{ {
"ImportPath": "github.com/vitrun/qart/coding", "ImportPath": "github.com/vitrun/qart/coding",
@@ -56,31 +59,19 @@
}, },
{ {
"ImportPath": "golang.org/x/crypto/bcrypt", "ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "81bf7719a6b7ce9b665598222362b50122dfc13b" "Rev": "c57d4a71915a248dbad846d60825145062b4c18e"
}, },
{ {
"ImportPath": "golang.org/x/crypto/blowfish", "ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "81bf7719a6b7ce9b665598222362b50122dfc13b" "Rev": "c57d4a71915a248dbad846d60825145062b4c18e"
},
{
"ImportPath": "golang.org/x/net/internal/iana",
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
},
{
"ImportPath": "golang.org/x/net/ipv6",
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
},
{
"ImportPath": "golang.org/x/net/proxy",
"Rev": "4b709d93778b93d2f34943e3142c71578d83ad31"
}, },
{ {
"ImportPath": "golang.org/x/text/transform", "ImportPath": "golang.org/x/text/transform",
"Rev": "723492b65e225eafcba054e76ba18bb9c5ac1ea2" "Rev": "2076e9cab4147459c82bc81169e46c139d358547"
}, },
{ {
"ImportPath": "golang.org/x/text/unicode/norm", "ImportPath": "golang.org/x/text/unicode/norm",
"Rev": "723492b65e225eafcba054e76ba18bb9c5ac1ea2" "Rev": "2076e9cab4147459c82bc81169e46c139d358547"
} }
] ]
} }
-2
View File
@@ -4,6 +4,4 @@ go:
- 1.1 - 1.1
- 1.2 - 1.2
- 1.3 - 1.3
- 1.4
- 1.5
- tip - tip
+1 -1
View File
@@ -4,7 +4,7 @@ go-lz4
go-lz4 is port of LZ4 lossless compression algorithm to Go. The original C code go-lz4 is port of LZ4 lossless compression algorithm to Go. The original C code
is located at: is located at:
https://github.com/Cyan4973/lz4 https://code.google.com/p/lz4/
Status Status
------ ------
-23
View File
@@ -1,23 +0,0 @@
// +build gofuzz
package lz4
import "encoding/binary"
func Fuzz(data []byte) int {
if len(data) < 4 {
return 0
}
ln := binary.LittleEndian.Uint32(data)
if ln > (1 << 21) {
return 0
}
if _, err := Decode(nil, data); err != nil {
return 0
}
return 1
}
+1 -6
View File
@@ -141,7 +141,7 @@ func Decode(dst, src []byte) ([]byte, error) {
length += ln length += ln
} }
if int(d.spos+length) > len(d.src) || int(d.dpos+length) > len(d.dst) { if int(d.spos+length) > len(d.src) {
return nil, ErrCorrupt return nil, ErrCorrupt
} }
@@ -179,12 +179,7 @@ func Decode(dst, src []byte) ([]byte, error) {
} }
literal := d.dpos - d.ref literal := d.dpos - d.ref
if literal < 4 { if literal < 4 {
if int(d.dpos+4) > len(d.dst) {
return nil, ErrCorrupt
}
d.cp(4, decr[literal]) d.cp(4, decr[literal])
} else { } else {
length += 4 length += 4
+2 -4
View File
@@ -25,10 +25,8 @@
package lz4 package lz4
import ( import "encoding/binary"
"encoding/binary" import "errors"
"errors"
)
const ( const (
minMatch = 4 minMatch = 4
-24
View File
@@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
-14
View File
@@ -1,14 +0,0 @@
du
==
Get total and available disk space on a given volume.
Documentation
-------------
http://godoc.org/github.com/calmh/du
License
-------
Public Domain
-21
View File
@@ -1,21 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"github.com/calmh/du"
)
var KB = int64(1024)
func main() {
usage, err := du.Get(os.Args[1])
if err != nil {
log.Fatal(err)
}
fmt.Println("Free:", usage.FreeBytes/(KB*KB), "MiB")
fmt.Println("Available:", usage.AvailBytes/(KB*KB), "MiB")
fmt.Println("Size:", usage.TotalBytes/(KB*KB), "MiB")
}
-8
View File
@@ -1,8 +0,0 @@
package du
// Usage holds information about total and available storage on a volume.
type Usage struct {
TotalBytes int64 // Size of volume
FreeBytes int64 // Unused size
AvailBytes int64 // Available to a non-privileged user
}
-24
View File
@@ -1,24 +0,0 @@
// +build !windows,!netbsd,!openbsd,!solaris
package du
import (
"path/filepath"
"syscall"
)
// Get returns the Usage of a given path, or an error if usage data is
// unavailable.
func Get(path string) (Usage, error) {
var stat syscall.Statfs_t
err := syscall.Statfs(filepath.Clean(path), &stat)
if err != nil {
return Usage{}, err
}
u := Usage{
FreeBytes: int64(stat.Bfree) * int64(stat.Bsize),
TotalBytes: int64(stat.Blocks) * int64(stat.Bsize),
AvailBytes: int64(stat.Bavail) * int64(stat.Bsize),
}
return u, nil
}
-13
View File
@@ -1,13 +0,0 @@
// +build netbsd openbsd solaris
package du
import "errors"
var ErrUnsupported = errors.New("unsupported platform")
// Get returns the Usage of a given path, or an error if usage data is
// unavailable.
func Get(path string) (Usage, error) {
return Usage{}, ErrUnsupported
}
-27
View File
@@ -1,27 +0,0 @@
package du
import (
"syscall"
"unsafe"
)
// Get returns the Usage of a given path, or an error if usage data is
// unavailable.
func Get(path string) (Usage, error) {
h := syscall.MustLoadDLL("kernel32.dll")
c := h.MustFindProc("GetDiskFreeSpaceExW")
var u Usage
ret, _, err := c.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
uintptr(unsafe.Pointer(&u.FreeBytes)),
uintptr(unsafe.Pointer(&u.TotalBytes)),
uintptr(unsafe.Pointer(&u.AvailBytes)))
if ret == 0 {
return u, err
}
return u, nil
}
+15
View File
@@ -0,0 +1,15 @@
logger
======
A small wrapper around `log` to provide log levels.
Documentation
-------------
http://godoc.org/github.com/calmh/logger
License
-------
MIT
+179
View File
@@ -0,0 +1,179 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
// Package logger implements a standardized logger with callback functionality
package logger
import (
"fmt"
"log"
"os"
"strings"
"sync"
)
type LogLevel int
const (
LevelDebug LogLevel = iota
LevelVerbose
LevelInfo
LevelOK
LevelWarn
LevelFatal
NumLevels
)
// A MessageHandler is called with the log level and message text.
type MessageHandler func(l LogLevel, msg string)
type Logger struct {
logger *log.Logger
handlers [NumLevels][]MessageHandler
mut sync.Mutex
}
// The default logger logs to standard output with a time prefix.
var DefaultLogger = New()
func New() *Logger {
return &Logger{
logger: log.New(os.Stdout, "", log.Ltime),
}
}
// AddHandler registers a new MessageHandler to receive messages with the
// specified log level or above.
func (l *Logger) AddHandler(level LogLevel, h MessageHandler) {
l.mut.Lock()
defer l.mut.Unlock()
l.handlers[level] = append(l.handlers[level], h)
}
// See log.SetFlags
func (l *Logger) SetFlags(flag int) {
l.logger.SetFlags(flag)
}
// See log.SetPrefix
func (l *Logger) SetPrefix(prefix string) {
l.logger.SetPrefix(prefix)
}
func (l *Logger) callHandlers(level LogLevel, s string) {
for _, h := range l.handlers[level] {
h(level, strings.TrimSpace(s))
}
}
// Debugln logs a line with a DEBUG prefix.
func (l *Logger) Debugln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "DEBUG: "+s)
l.callHandlers(LevelDebug, s)
}
// Debugf logs a formatted line with a DEBUG prefix.
func (l *Logger) Debugf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "DEBUG: "+s)
l.callHandlers(LevelDebug, s)
}
// Infoln logs a line with a VERBOSE prefix.
func (l *Logger) Verboseln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "VERBOSE: "+s)
l.callHandlers(LevelVerbose, s)
}
// Infof logs a formatted line with a VERBOSE prefix.
func (l *Logger) Verbosef(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "VERBOSE: "+s)
l.callHandlers(LevelVerbose, s)
}
// Infoln logs a line with an INFO prefix.
func (l *Logger) Infoln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "INFO: "+s)
l.callHandlers(LevelInfo, s)
}
// Infof logs a formatted line with an INFO prefix.
func (l *Logger) Infof(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "INFO: "+s)
l.callHandlers(LevelInfo, s)
}
// Okln logs a line with an OK prefix.
func (l *Logger) Okln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "OK: "+s)
l.callHandlers(LevelOK, s)
}
// Okf logs a formatted line with an OK prefix.
func (l *Logger) Okf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "OK: "+s)
l.callHandlers(LevelOK, s)
}
// Warnln logs a formatted line with a WARNING prefix.
func (l *Logger) Warnln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "WARNING: "+s)
l.callHandlers(LevelWarn, s)
}
// Warnf logs a formatted line with a WARNING prefix.
func (l *Logger) Warnf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "WARNING: "+s)
l.callHandlers(LevelWarn, s)
}
// Fatalln logs a line with a FATAL prefix and exits the process with exit
// code 1.
func (l *Logger) Fatalln(vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintln(vals...)
l.logger.Output(2, "FATAL: "+s)
l.callHandlers(LevelFatal, s)
os.Exit(1)
}
// Fatalf logs a formatted line with a FATAL prefix and exits the process with
// exit code 1.
func (l *Logger) Fatalf(format string, vals ...interface{}) {
l.mut.Lock()
defer l.mut.Unlock()
s := fmt.Sprintf(format, vals...)
l.logger.Output(2, "FATAL: "+s)
l.callHandlers(LevelFatal, s)
os.Exit(1)
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
// is governed by an MIT-style license that can be found in the LICENSE file.
package logger
import (
"strings"
"testing"
)
func TestAPI(t *testing.T) {
l := New()
l.SetFlags(0)
l.SetPrefix("testing")
debug := 0
l.AddHandler(LevelDebug, checkFunc(t, LevelDebug, "test 0", &debug))
info := 0
l.AddHandler(LevelInfo, checkFunc(t, LevelInfo, "test 1", &info))
warn := 0
l.AddHandler(LevelWarn, checkFunc(t, LevelWarn, "test 2", &warn))
ok := 0
l.AddHandler(LevelOK, checkFunc(t, LevelOK, "test 3", &ok))
l.Debugf("test %d", 0)
l.Debugln("test", 0)
l.Infof("test %d", 1)
l.Infoln("test", 1)
l.Warnf("test %d", 2)
l.Warnln("test", 2)
l.Okf("test %d", 3)
l.Okln("test", 3)
if debug != 2 {
t.Errorf("Debug handler called %d != 2 times", debug)
}
if info != 2 {
t.Errorf("Info handler called %d != 2 times", info)
}
if warn != 2 {
t.Errorf("Warn handler called %d != 2 times", warn)
}
if ok != 2 {
t.Errorf("Ok handler called %d != 2 times", ok)
}
}
func checkFunc(t *testing.T, expectl LogLevel, expectmsg string, counter *int) func(LogLevel, string) {
return func(l LogLevel, msg string) {
*counter++
if l != expectl {
t.Errorf("Incorrect message level %d != %d", l, expectl)
}
if !strings.HasSuffix(msg, expectmsg) {
t.Errorf("%q does not end with %q", msg, expectmsg)
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ go:
install: install:
- export PATH=$PATH:$HOME/gopath/bin - export PATH=$PATH:$HOME/gopath/bin
- go get golang.org/x/tools/cover - go get code.google.com/p/go.tools/cmd/cover
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
script: script:
+1 -1
View File
@@ -1,7 +1,7 @@
xdr xdr
=== ===
[![Build Status](https://img.shields.io/circleci/project/calmh/xdr.svg?style=flat-square)](https://circleci.com/gh/calmh/xdr) [![Build Status](https://img.shields.io/travis/calmh/xdr.svg?style=flat)](https://travis-ci.org/calmh/xdr)
[![Coverage Status](https://img.shields.io/coveralls/calmh/xdr.svg?style=flat)](https://coveralls.io/r/calmh/xdr?branch=master) [![Coverage Status](https://img.shields.io/coveralls/calmh/xdr.svg?style=flat)](https://coveralls.io/r/calmh/xdr?branch=master)
[![API Documentation](http://img.shields.io/badge/api-Godoc-blue.svg?style=flat)](http://godoc.org/github.com/calmh/xdr) [![API Documentation](http://img.shields.io/badge/api-Godoc-blue.svg?style=flat)](http://godoc.org/github.com/calmh/xdr)
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT)
+11 -26
View File
@@ -28,7 +28,6 @@ type fieldInfo struct {
Encoder string // the encoder name, i.e. "Uint64" for Read/WriteUint64 Encoder string // the encoder name, i.e. "Uint64" for Read/WriteUint64
Convert string // what to convert to when encoding, i.e. "uint64" Convert string // what to convert to when encoding, i.e. "uint64"
Max int // max size for slices and strings Max int // max size for slices and strings
Submax int // max size for strings inside slices
} }
type structInfo struct { type structInfo struct {
@@ -157,11 +156,7 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
{{if ne $fieldInfo.Convert ""}} {{if ne $fieldInfo.Convert ""}}
o.{{$fieldInfo.Name}}[i] = {{$fieldInfo.FieldType}}(xr.Read{{$fieldInfo.Encoder}}()) o.{{$fieldInfo.Name}}[i] = {{$fieldInfo.FieldType}}(xr.Read{{$fieldInfo.Encoder}}())
{{else if $fieldInfo.IsBasic}} {{else if $fieldInfo.IsBasic}}
{{if ge $fieldInfo.Submax 1}} o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}Max({{$fieldInfo.Submax}})
{{else}}
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
{{end}}
{{else}} {{else}}
(&o.{{$fieldInfo.Name}}[i]).DecodeXDRFrom(xr) (&o.{{$fieldInfo.Name}}[i]).DecodeXDRFrom(xr)
{{end}} {{end}}
@@ -171,7 +166,7 @@ func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
return xr.Error() return xr.Error()
}`)) }`))
var maxRe = regexp.MustCompile(`(?:\Wmax:)(\d+)(?:\s*,\s*(\d+))?`) var maxRe = regexp.MustCompile(`\Wmax:(\d+)`)
type typeSet struct { type typeSet struct {
Type string Type string
@@ -203,15 +198,11 @@ func handleStruct(t *ast.StructType) []fieldInfo {
} }
fn := sf.Names[0].Name fn := sf.Names[0].Name
var max1, max2 int var max = 0
if sf.Comment != nil { if sf.Comment != nil {
c := sf.Comment.List[0].Text c := sf.Comment.List[0].Text
m := maxRe.FindStringSubmatch(c) if m := maxRe.FindStringSubmatch(c); m != nil {
if len(m) >= 2 { max, _ = strconv.Atoi(m[1])
max1, _ = strconv.Atoi(m[1])
}
if len(m) >= 3 {
max2, _ = strconv.Atoi(m[2])
} }
if strings.Contains(c, "noencode") { if strings.Contains(c, "noencode") {
continue continue
@@ -229,16 +220,14 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn, FieldType: tn,
Encoder: enc.Encoder, Encoder: enc.Encoder,
Convert: enc.Type, Convert: enc.Type,
Max: max1, Max: max,
Submax: max2,
} }
} else { } else {
f = fieldInfo{ f = fieldInfo{
Name: fn, Name: fn,
IsBasic: false, IsBasic: false,
FieldType: tn, FieldType: tn,
Max: max1, Max: max,
Submax: max2,
} }
} }
@@ -256,8 +245,7 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn, FieldType: tn,
Encoder: enc.Encoder, Encoder: enc.Encoder,
Convert: enc.Type, Convert: enc.Type,
Max: max1, Max: max,
Submax: max2,
} }
} else if enc, ok := xdrEncoders[tn]; ok { } else if enc, ok := xdrEncoders[tn]; ok {
f = fieldInfo{ f = fieldInfo{
@@ -267,16 +255,14 @@ func handleStruct(t *ast.StructType) []fieldInfo {
FieldType: tn, FieldType: tn,
Encoder: enc.Encoder, Encoder: enc.Encoder,
Convert: enc.Type, Convert: enc.Type,
Max: max1, Max: max,
Submax: max2,
} }
} else { } else {
f = fieldInfo{ f = fieldInfo{
Name: fn, Name: fn,
IsSlice: true, IsSlice: true,
FieldType: tn, FieldType: tn,
Max: max1, Max: max,
Submax: max2,
} }
} }
@@ -284,8 +270,7 @@ func handleStruct(t *ast.StructType) []fieldInfo {
f = fieldInfo{ f = fieldInfo{
Name: fn, Name: fn,
FieldType: ft.Sel.Name, FieldType: ft.Sel.Name,
Max: max1, Max: max,
Submax: max2,
} }
} }
-14
View File
@@ -1,14 +0,0 @@
# This is the official list of Snappy-Go authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
Damian Gryski <dgryski@gmail.com>
Google Inc.
Jan Mercl <0xjnml@gmail.com>
Sebastien Binet <seb.binet@gmail.com>
-36
View File
@@ -1,36 +0,0 @@
# This is the official list of people who can contribute
# (and typically have contributed) code to the Snappy-Go repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# http://code.google.com/legal/individual-cla-v1.0.html
# http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Name <email address>
# Please keep the list sorted.
Damian Gryski <dgryski@gmail.com>
Jan Mercl <0xjnml@gmail.com>
Kai Backman <kaib@golang.org>
Marc-Antoine Ruel <maruel@chromium.org>
Nigel Tao <nigeltao@golang.org>
Rob Pike <r@golang.org>
Russ Cox <rsc@golang.org>
Sebastien Binet <seb.binet@gmail.com>
-27
View File
@@ -1,27 +0,0 @@
Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-7
View File
@@ -1,7 +0,0 @@
The Snappy compression format in the Go programming language.
To download and install from source:
$ go get github.com/golang/snappy
Unless otherwise noted, the Snappy-Go source files are distributed
under the BSD-style license found in the LICENSE file.
+2 -5
View File
@@ -1,8 +1,5 @@
All files in this repository are licensed as follows. If you contribute This package contains an efficient token-bucket-based rate limiter.
to this repository, it is assumed that you license your contribution Copyright (C) 2015 Canonical Ltd.
under the same license unless you state otherwise.
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
This software is licensed under the LGPLv3, included below. This software is licensed under the LGPLv3, included below.
+1 -1
View File
@@ -20,7 +20,7 @@ token in the bucket represents one byte.
```go ```go
func Writer(w io.Writer, bucket *Bucket) io.Writer func Writer(w io.Writer, bucket *Bucket) io.Writer
``` ```
Writer returns a writer that is rate limited by the given token bucket. Each Writer returns a reader that is rate limited by the given token bucket. Each
token in the bucket represents one byte. token in the bucket represents one byte.
#### type Bucket #### type Bucket
+9 -4
View File
@@ -2,8 +2,7 @@
// Licensed under the LGPLv3 with static-linking exception. // Licensed under the LGPLv3 with static-linking exception.
// See LICENCE file for details. // See LICENCE file for details.
// The ratelimit package provides an efficient token bucket implementation // The ratelimit package provides an efficient token bucket implementation.
// that can be used to limit the rate of arbitrary things.
// See http://en.wikipedia.org/wiki/Token_bucket. // See http://en.wikipedia.org/wiki/Token_bucket.
package ratelimit package ratelimit
@@ -11,7 +10,6 @@ import (
"strconv" "strconv"
"sync" "sync"
"time" "time"
"math"
) )
// Bucket represents a token bucket that fills at a predetermined rate. // Bucket represents a token bucket that fills at a predetermined rate.
@@ -57,7 +55,7 @@ func NewBucketWithRate(rate float64, capacity int64) *Bucket {
continue continue
} }
tb := NewBucketWithQuantum(fillInterval, capacity, quantum) tb := NewBucketWithQuantum(fillInterval, capacity, quantum)
if diff := math.Abs(tb.Rate() - rate); diff/rate <= rateMargin { if diff := abs(tb.Rate() - rate); diff/rate <= rateMargin {
return tb return tb
} }
} }
@@ -219,3 +217,10 @@ func (tb *Bucket) adjust(now time.Time) (currentTick int64) {
tb.availTick = currentTick tb.availTick = currentTick
return return
} }
func abs(f float64) float64 {
if f < 0 {
return -f
}
return f
}
+1 -3
View File
@@ -4,9 +4,7 @@
There is sometimes utility in finding the current executable file There is sometimes utility in finding the current executable file
that is running. This can be used for upgrading the current executable that is running. This can be used for upgrading the current executable
or finding resources located relative to the executable file. Both or finding resources located relative to the executable file.
working directory and the os.Args[0] value are arbitrary and cannot
be relied on; os.Args[0] can be "faked".
Multi-platform and supports: Multi-platform and supports:
* Linux * Linux
+3 -3
View File
@@ -16,12 +16,12 @@ func Executable() (string, error) {
} }
// Returns same path as Executable, returns just the folder // Returns same path as Executable, returns just the folder
// path. Excludes the executable name and any trailing slash. // path. Excludes the executable name.
func ExecutableFolder() (string, error) { func ExecutableFolder() (string, error) {
p, err := Executable() p, err := Executable()
if err != nil { if err != nil {
return "", err return "", err
} }
folder, _ := filepath.Split(p)
return filepath.Dir(p), nil return folder, nil
} }
+2 -4
View File
@@ -17,14 +17,12 @@ import (
func executable() (string, error) { func executable() (string, error) {
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "linux":
const deletedTag = " (deleted)" const deletedSuffix = " (deleted)"
execpath, err := os.Readlink("/proc/self/exe") execpath, err := os.Readlink("/proc/self/exe")
if err != nil { if err != nil {
return execpath, err return execpath, err
} }
execpath = strings.TrimSuffix(execpath, deletedTag) return strings.TrimSuffix(execpath, deletedSuffix), nil
execpath = strings.TrimPrefix(execpath, deletedTag)
return execpath, nil
case "netbsd": case "netbsd":
return os.Readlink("/proc/curproc/exe") return os.Readlink("/proc/curproc/exe")
case "openbsd", "dragonfly": case "openbsd", "dragonfly":
-23
View File
@@ -24,29 +24,6 @@ const (
executableEnvValueDelete = "delete" executableEnvValueDelete = "delete"
) )
func TestPrintExecutable(t *testing.T) {
ef, err := Executable()
if err != nil {
t.Fatalf("Executable failed: %v", err)
}
t.Log("Executable:", ef)
}
func TestPrintExecutableFolder(t *testing.T) {
ef, err := ExecutableFolder()
if err != nil {
t.Fatalf("ExecutableFolder failed: %v", err)
}
t.Log("Executable Folder:", ef)
}
func TestExecutableFolder(t *testing.T) {
ef, err := ExecutableFolder()
if err != nil {
t.Fatalf("ExecutableFolder failed: %v", err)
}
if ef[len(ef)-1] == filepath.Separator {
t.Fatal("ExecutableFolder ends with a trailing slash.")
}
}
func TestExecutableMatch(t *testing.T) { func TestExecutableMatch(t *testing.T) {
ep, err := Executable() ep, err := Executable()
if err != nil { if err != nil {
@@ -31,16 +31,15 @@ func (t *TestModel) Index(deviceID DeviceID, folder string, files []FileInfo, fl
func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) { func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
} }
func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error { func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
t.folder = folder t.folder = folder
t.name = name t.name = name
t.offset = offset t.offset = offset
t.size = len(buf) t.size = size
t.hash = hash t.hash = hash
t.flags = flags t.flags = flags
t.options = options t.options = options
copy(buf, t.data) return t.data, nil
return nil
} }
func (t *TestModel) Close(deviceID DeviceID, err error) { func (t *TestModel) Close(deviceID DeviceID, err error) {
+15
View File
@@ -0,0 +1,15 @@
// Copyright (C) 2014 The Protocol Authors.
package protocol
import (
"os"
"strings"
"github.com/calmh/logger"
)
var (
debug = strings.Contains(os.Getenv("STTRACE"), "protocol") || os.Getenv("STTRACE") == "all"
l = logger.DefaultLogger
)
+4 -4
View File
@@ -14,10 +14,10 @@ const (
) )
var ( var (
ErrNoError error ErrNoError error = nil
ErrGeneric = errors.New("generic error") ErrGeneric = errors.New("generic error")
ErrNoSuchFile = errors.New("no such file") ErrNoSuchFile = errors.New("no such file")
ErrInvalid = errors.New("file is invalid") ErrInvalid = errors.New("file is invalid")
) )
var lookupError = map[int32]error{ var lookupError = map[int32]error{
@@ -1,6 +1,6 @@
// Copyright (C) 2014 The Protocol Authors. // Copyright (C) 2014 The Protocol Authors.
//go:generate -command genxdr go run ../../Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go //go:generate -command genxdr go run ../syncthing/Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
//go:generate genxdr -o message_xdr.go message.go //go:generate genxdr -o message_xdr.go message.go
package protocol package protocol
@@ -8,8 +8,8 @@ package protocol
import "fmt" import "fmt"
type IndexMessage struct { type IndexMessage struct {
Folder string // max:256 Folder string
Files []FileInfo // max:1000000 Files []FileInfo
Flags uint32 Flags uint32
Options []Option // max:64 Options []Option // max:64
} }
@@ -20,8 +20,7 @@ type FileInfo struct {
Modified int64 Modified int64
Version Vector Version Vector
LocalVersion int64 LocalVersion int64
CachedSize int64 // noencode (cache only) Blocks []BlockInfo
Blocks []BlockInfo // max:1000000
} }
func (f FileInfo) String() string { func (f FileInfo) String() string {
@@ -33,13 +32,9 @@ func (f FileInfo) Size() (bytes int64) {
if f.IsDeleted() || f.IsDirectory() { if f.IsDeleted() || f.IsDirectory() {
return 128 return 128
} }
if f.CachedSize > 0 {
return f.CachedSize
}
for _, b := range f.Blocks { for _, b := range f.Blocks {
bytes += int64(b.Size) bytes += int64(b.Size)
} }
f.CachedSize = bytes
return return
} }
@@ -63,31 +58,6 @@ func (f FileInfo) HasPermissionBits() bool {
return f.Flags&FlagNoPermBits == 0 return f.Flags&FlagNoPermBits == 0
} }
// WinsConflict returns true if "f" is the one to choose when it is in
// conflict with "other".
func (f FileInfo) WinsConflict(other FileInfo) bool {
// If a modification is in conflict with a delete, we pick the
// modification.
if !f.IsDeleted() && other.IsDeleted() {
return true
}
if f.IsDeleted() && !other.IsDeleted() {
return false
}
// The one with the newer modification time wins.
if f.Modified > other.Modified {
return true
}
if f.Modified < other.Modified {
return false
}
// The modification times were equal. Use the device ID in the version
// vector as tie breaker.
return f.Version.Compare(other.Version) == ConcurrentGreater
}
type BlockInfo struct { type BlockInfo struct {
Offset int64 // noencode (cache only) Offset int64 // noencode (cache only)
Size int32 Size int32
@@ -99,7 +69,7 @@ func (b BlockInfo) String() string {
} }
type RequestMessage struct { type RequestMessage struct {
Folder string // max:256 Folder string // max:64
Name string // max:8192 Name string // max:8192
Offset int64 Offset int64
Size int32 Size int32
@@ -114,10 +84,9 @@ type ResponseMessage struct {
} }
type ClusterConfigMessage struct { type ClusterConfigMessage struct {
DeviceName string // max:64 ClientName string // max:64
ClientName string // max:64 ClientVersion string // max:64
ClientVersion string // max:64 Folders []Folder
Folders []Folder // max:1000000
Options []Option // max:64 Options []Option // max:64
} }
@@ -131,18 +100,14 @@ func (o *ClusterConfigMessage) GetOption(key string) string {
} }
type Folder struct { type Folder struct {
ID string // max:256 ID string // max:64
Devices []Device // max:1000000 Devices []Device
Flags uint32 Flags uint32
Options []Option // max:64 Options []Option // max:64
} }
type Device struct { type Device struct {
ID []byte // max:32 ID []byte // max:32
Name string // max:64
Addresses []string // max:64,2083
Compression uint32
CertName string // max:64
MaxLocalVersion int64 MaxLocalVersion int64
Flags uint32 Flags uint32
Options []Option // max:64 Options []Option // max:64
@@ -41,8 +41,8 @@ IndexMessage Structure:
struct IndexMessage { struct IndexMessage {
string Folder<256>; string Folder<>;
FileInfo Files<1000000>; FileInfo Files<>;
unsigned int Flags; unsigned int Flags;
Option Options<64>; Option Options<64>;
} }
@@ -74,13 +74,7 @@ func (o IndexMessage) AppendXDR(bs []byte) ([]byte, error) {
} }
func (o IndexMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) { func (o IndexMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.Folder); l > 256 {
return xw.Tot(), xdr.ElementSizeExceeded("Folder", l, 256)
}
xw.WriteString(o.Folder) xw.WriteString(o.Folder)
if l := len(o.Files); l > 1000000 {
return xw.Tot(), xdr.ElementSizeExceeded("Files", l, 1000000)
}
xw.WriteUint32(uint32(len(o.Files))) xw.WriteUint32(uint32(len(o.Files)))
for i := range o.Files { for i := range o.Files {
_, err := o.Files[i].EncodeXDRInto(xw) _, err := o.Files[i].EncodeXDRInto(xw)
@@ -114,13 +108,10 @@ func (o *IndexMessage) UnmarshalXDR(bs []byte) error {
} }
func (o *IndexMessage) DecodeXDRFrom(xr *xdr.Reader) error { func (o *IndexMessage) DecodeXDRFrom(xr *xdr.Reader) error {
o.Folder = xr.ReadStringMax(256) o.Folder = xr.ReadString()
_FilesSize := int(xr.ReadUint32()) _FilesSize := int(xr.ReadUint32())
if _FilesSize < 0 { if _FilesSize < 0 {
return xdr.ElementSizeExceeded("Files", _FilesSize, 1000000) return xdr.ElementSizeExceeded("Files", _FilesSize, 0)
}
if _FilesSize > 1000000 {
return xdr.ElementSizeExceeded("Files", _FilesSize, 1000000)
} }
o.Files = make([]FileInfo, _FilesSize) o.Files = make([]FileInfo, _FilesSize)
for i := range o.Files { for i := range o.Files {
@@ -182,7 +173,7 @@ struct FileInfo {
hyper Modified; hyper Modified;
Vector Version; Vector Version;
hyper LocalVersion; hyper LocalVersion;
BlockInfo Blocks<1000000>; BlockInfo Blocks<>;
} }
*/ */
@@ -223,9 +214,6 @@ func (o FileInfo) EncodeXDRInto(xw *xdr.Writer) (int, error) {
return xw.Tot(), err return xw.Tot(), err
} }
xw.WriteUint64(uint64(o.LocalVersion)) xw.WriteUint64(uint64(o.LocalVersion))
if l := len(o.Blocks); l > 1000000 {
return xw.Tot(), xdr.ElementSizeExceeded("Blocks", l, 1000000)
}
xw.WriteUint32(uint32(len(o.Blocks))) xw.WriteUint32(uint32(len(o.Blocks)))
for i := range o.Blocks { for i := range o.Blocks {
_, err := o.Blocks[i].EncodeXDRInto(xw) _, err := o.Blocks[i].EncodeXDRInto(xw)
@@ -255,10 +243,7 @@ func (o *FileInfo) DecodeXDRFrom(xr *xdr.Reader) error {
o.LocalVersion = int64(xr.ReadUint64()) o.LocalVersion = int64(xr.ReadUint64())
_BlocksSize := int(xr.ReadUint32()) _BlocksSize := int(xr.ReadUint32())
if _BlocksSize < 0 { if _BlocksSize < 0 {
return xdr.ElementSizeExceeded("Blocks", _BlocksSize, 1000000) return xdr.ElementSizeExceeded("Blocks", _BlocksSize, 0)
}
if _BlocksSize > 1000000 {
return xdr.ElementSizeExceeded("Blocks", _BlocksSize, 1000000)
} }
o.Blocks = make([]BlockInfo, _BlocksSize) o.Blocks = make([]BlockInfo, _BlocksSize)
for i := range o.Blocks { for i := range o.Blocks {
@@ -383,7 +368,7 @@ RequestMessage Structure:
struct RequestMessage { struct RequestMessage {
string Folder<256>; string Folder<64>;
string Name<8192>; string Name<8192>;
hyper Offset; hyper Offset;
int Size; int Size;
@@ -419,8 +404,8 @@ func (o RequestMessage) AppendXDR(bs []byte) ([]byte, error) {
} }
func (o RequestMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) { func (o RequestMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.Folder); l > 256 { if l := len(o.Folder); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Folder", l, 256) return xw.Tot(), xdr.ElementSizeExceeded("Folder", l, 64)
} }
xw.WriteString(o.Folder) xw.WriteString(o.Folder)
if l := len(o.Name); l > 8192 { if l := len(o.Name); l > 8192 {
@@ -459,7 +444,7 @@ func (o *RequestMessage) UnmarshalXDR(bs []byte) error {
} }
func (o *RequestMessage) DecodeXDRFrom(xr *xdr.Reader) error { func (o *RequestMessage) DecodeXDRFrom(xr *xdr.Reader) error {
o.Folder = xr.ReadStringMax(256) o.Folder = xr.ReadStringMax(64)
o.Name = xr.ReadStringMax(8192) o.Name = xr.ReadStringMax(8192)
o.Offset = int64(xr.ReadUint64()) o.Offset = int64(xr.ReadUint64())
o.Size = int32(xr.ReadUint32()) o.Size = int32(xr.ReadUint32())
@@ -557,12 +542,6 @@ ClusterConfigMessage Structure:
0 1 2 3 0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Device Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Device Name (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Client Name | | Length of Client Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ / / /
@@ -590,10 +569,9 @@ ClusterConfigMessage Structure:
struct ClusterConfigMessage { struct ClusterConfigMessage {
string DeviceName<64>;
string ClientName<64>; string ClientName<64>;
string ClientVersion<64>; string ClientVersion<64>;
Folder Folders<1000000>; Folder Folders<>;
Option Options<64>; Option Options<64>;
} }
@@ -624,10 +602,6 @@ func (o ClusterConfigMessage) AppendXDR(bs []byte) ([]byte, error) {
} }
func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) { func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.DeviceName); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("DeviceName", l, 64)
}
xw.WriteString(o.DeviceName)
if l := len(o.ClientName); l > 64 { if l := len(o.ClientName); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("ClientName", l, 64) return xw.Tot(), xdr.ElementSizeExceeded("ClientName", l, 64)
} }
@@ -636,9 +610,6 @@ func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("ClientVersion", l, 64) return xw.Tot(), xdr.ElementSizeExceeded("ClientVersion", l, 64)
} }
xw.WriteString(o.ClientVersion) xw.WriteString(o.ClientVersion)
if l := len(o.Folders); l > 1000000 {
return xw.Tot(), xdr.ElementSizeExceeded("Folders", l, 1000000)
}
xw.WriteUint32(uint32(len(o.Folders))) xw.WriteUint32(uint32(len(o.Folders)))
for i := range o.Folders { for i := range o.Folders {
_, err := o.Folders[i].EncodeXDRInto(xw) _, err := o.Folders[i].EncodeXDRInto(xw)
@@ -671,15 +642,11 @@ func (o *ClusterConfigMessage) UnmarshalXDR(bs []byte) error {
} }
func (o *ClusterConfigMessage) DecodeXDRFrom(xr *xdr.Reader) error { func (o *ClusterConfigMessage) DecodeXDRFrom(xr *xdr.Reader) error {
o.DeviceName = xr.ReadStringMax(64)
o.ClientName = xr.ReadStringMax(64) o.ClientName = xr.ReadStringMax(64)
o.ClientVersion = xr.ReadStringMax(64) o.ClientVersion = xr.ReadStringMax(64)
_FoldersSize := int(xr.ReadUint32()) _FoldersSize := int(xr.ReadUint32())
if _FoldersSize < 0 { if _FoldersSize < 0 {
return xdr.ElementSizeExceeded("Folders", _FoldersSize, 1000000) return xdr.ElementSizeExceeded("Folders", _FoldersSize, 0)
}
if _FoldersSize > 1000000 {
return xdr.ElementSizeExceeded("Folders", _FoldersSize, 1000000)
} }
o.Folders = make([]Folder, _FoldersSize) o.Folders = make([]Folder, _FoldersSize)
for i := range o.Folders { for i := range o.Folders {
@@ -729,8 +696,8 @@ Folder Structure:
struct Folder { struct Folder {
string ID<256>; string ID<64>;
Device Devices<1000000>; Device Devices<>;
unsigned int Flags; unsigned int Flags;
Option Options<64>; Option Options<64>;
} }
@@ -762,13 +729,10 @@ func (o Folder) AppendXDR(bs []byte) ([]byte, error) {
} }
func (o Folder) EncodeXDRInto(xw *xdr.Writer) (int, error) { func (o Folder) EncodeXDRInto(xw *xdr.Writer) (int, error) {
if l := len(o.ID); l > 256 { if l := len(o.ID); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 256) return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 64)
} }
xw.WriteString(o.ID) xw.WriteString(o.ID)
if l := len(o.Devices); l > 1000000 {
return xw.Tot(), xdr.ElementSizeExceeded("Devices", l, 1000000)
}
xw.WriteUint32(uint32(len(o.Devices))) xw.WriteUint32(uint32(len(o.Devices)))
for i := range o.Devices { for i := range o.Devices {
_, err := o.Devices[i].EncodeXDRInto(xw) _, err := o.Devices[i].EncodeXDRInto(xw)
@@ -802,13 +766,10 @@ func (o *Folder) UnmarshalXDR(bs []byte) error {
} }
func (o *Folder) DecodeXDRFrom(xr *xdr.Reader) error { func (o *Folder) DecodeXDRFrom(xr *xdr.Reader) error {
o.ID = xr.ReadStringMax(256) o.ID = xr.ReadStringMax(64)
_DevicesSize := int(xr.ReadUint32()) _DevicesSize := int(xr.ReadUint32())
if _DevicesSize < 0 { if _DevicesSize < 0 {
return xdr.ElementSizeExceeded("Devices", _DevicesSize, 1000000) return xdr.ElementSizeExceeded("Devices", _DevicesSize, 0)
}
if _DevicesSize > 1000000 {
return xdr.ElementSizeExceeded("Devices", _DevicesSize, 1000000)
} }
o.Devices = make([]Device, _DevicesSize) o.Devices = make([]Device, _DevicesSize)
for i := range o.Devices { for i := range o.Devices {
@@ -842,28 +803,6 @@ Device Structure:
\ ID (variable length) \ \ ID (variable length) \
/ / / /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Name (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Addresses |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Addresses (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Compression |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Cert Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Cert Name (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | | |
+ Max Local Version (64 bits) + + Max Local Version (64 bits) +
| | | |
@@ -880,10 +819,6 @@ Device Structure:
struct Device { struct Device {
opaque ID<32>; opaque ID<32>;
string Name<64>;
string Addresses<64>;
unsigned int Compression;
string CertName<64>;
hyper MaxLocalVersion; hyper MaxLocalVersion;
unsigned int Flags; unsigned int Flags;
Option Options<64>; Option Options<64>;
@@ -920,22 +855,6 @@ func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32) return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
} }
xw.WriteBytes(o.ID) xw.WriteBytes(o.ID)
if l := len(o.Name); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 64)
}
xw.WriteString(o.Name)
if l := len(o.Addresses); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Addresses", l, 64)
}
xw.WriteUint32(uint32(len(o.Addresses)))
for i := range o.Addresses {
xw.WriteString(o.Addresses[i])
}
xw.WriteUint32(o.Compression)
if l := len(o.CertName); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("CertName", l, 64)
}
xw.WriteString(o.CertName)
xw.WriteUint64(uint64(o.MaxLocalVersion)) xw.WriteUint64(uint64(o.MaxLocalVersion))
xw.WriteUint32(o.Flags) xw.WriteUint32(o.Flags)
if l := len(o.Options); l > 64 { if l := len(o.Options); l > 64 {
@@ -964,20 +883,6 @@ func (o *Device) UnmarshalXDR(bs []byte) error {
func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error { func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
o.ID = xr.ReadBytesMax(32) o.ID = xr.ReadBytesMax(32)
o.Name = xr.ReadStringMax(64)
_AddressesSize := int(xr.ReadUint32())
if _AddressesSize < 0 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 64)
}
if _AddressesSize > 64 {
return xdr.ElementSizeExceeded("Addresses", _AddressesSize, 64)
}
o.Addresses = make([]string, _AddressesSize)
for i := range o.Addresses {
o.Addresses[i] = xr.ReadStringMax(2083)
}
o.Compression = xr.ReadUint32()
o.CertName = xr.ReadStringMax(64)
o.MaxLocalVersion = int64(xr.ReadUint64()) o.MaxLocalVersion = int64(xr.ReadUint64())
o.Flags = xr.ReadUint32() o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32()) _OptionsSize := int(xr.ReadUint32())
@@ -26,9 +26,9 @@ func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileI
m.next.IndexUpdate(deviceID, folder, files, flags, options) m.next.IndexUpdate(deviceID, folder, files, flags, options)
} }
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error { func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
name = norm.NFD.String(name) name = norm.NFD.String(name)
return m.next.Request(deviceID, folder, name, offset, hash, flags, options, buf) return m.next.Request(deviceID, folder, name, offset, size, hash, flags, options)
} }
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) { func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
@@ -18,8 +18,8 @@ func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileI
m.next.IndexUpdate(deviceID, folder, files, flags, options) m.next.IndexUpdate(deviceID, folder, files, flags, options)
} }
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error { func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
return m.next.Request(deviceID, folder, name, offset, hash, flags, options, buf) return m.next.Request(deviceID, folder, name, offset, size, hash, flags, options)
} }
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) { func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
@@ -25,18 +25,18 @@ type nativeModel struct {
} }
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) { func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
fixupFiles(folder, files) fixupFiles(files)
m.next.Index(deviceID, folder, files, flags, options) m.next.Index(deviceID, folder, files, flags, options)
} }
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) { func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
fixupFiles(folder, files) fixupFiles(files)
m.next.IndexUpdate(deviceID, folder, files, flags, options) m.next.IndexUpdate(deviceID, folder, files, flags, options)
} }
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error { func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
name = filepath.FromSlash(name) name = filepath.FromSlash(name)
return m.next.Request(deviceID, folder, name, offset, hash, flags, options, buf) return m.next.Request(deviceID, folder, name, offset, size, hash, flags, options)
} }
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) { func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
@@ -47,7 +47,7 @@ func (m nativeModel) Close(deviceID DeviceID, err error) {
m.next.Close(deviceID, err) m.next.Close(deviceID, err)
} }
func fixupFiles(folder string, files []FileInfo) { func fixupFiles(files []FileInfo) {
for i, f := range files { for i, f := range files {
if strings.ContainsAny(f.Name, disallowedCharacters) { if strings.ContainsAny(f.Name, disallowedCharacters) {
if f.IsDeleted() { if f.IsDeleted() {
@@ -56,7 +56,7 @@ func fixupFiles(folder string, files []FileInfo) {
continue continue
} }
files[i].Flags |= FlagInvalid files[i].Flags |= FlagInvalid
l.Warnf("File name %q (folder %q) contains invalid characters; marked as invalid.", f.Name, folder) l.Warnf("File name %q contains invalid characters; marked as invalid.", f.Name)
} }
files[i].Name = filepath.FromSlash(files[i].Name) files[i].Name = filepath.FromSlash(files[i].Name)
} }
@@ -15,11 +15,7 @@ import (
) )
const ( const (
// BlockSize is the standard ata block size (128 KiB) BlockSize = 128 * 1024
BlockSize = 128 << 10
// MaxMessageLen is the largest message size allowed on the wire. (64 MiB)
MaxMessageLen = 64 << 20
) )
const ( const (
@@ -28,13 +24,15 @@ const (
messageTypeRequest = 2 messageTypeRequest = 2
messageTypeResponse = 3 messageTypeResponse = 3
messageTypePing = 4 messageTypePing = 4
messageTypePong = 5
messageTypeIndexUpdate = 6 messageTypeIndexUpdate = 6
messageTypeClose = 7 messageTypeClose = 7
) )
const ( const (
stateInitial = iota stateInitial = iota
stateReady stateCCRcvd
stateIdxRcvd
) )
// FileInfo flags // FileInfo flags
@@ -61,13 +59,6 @@ const (
FlagRequestTemporary uint32 = 1 << iota FlagRequestTemporary uint32 = 1 << iota
) )
// ClusterConfigMessage.Folders flags
const (
FlagFolderReadOnly uint32 = 1 << 0
FlagFolderIgnorePerms = 1 << 1
FlagFolderIgnoreDelete = 1 << 2
)
// ClusterConfigMessage.Folders.Devices flags // ClusterConfigMessage.Folders.Devices flags
const ( const (
FlagShareTrusted uint32 = 1 << 0 FlagShareTrusted uint32 = 1 << 0
@@ -77,12 +68,13 @@ const (
) )
var ( var (
ErrClosed = errors.New("connection closed") ErrClusterHash = fmt.Errorf("configuration error: mismatched cluster hash")
ErrTimeout = errors.New("read timeout") ErrClosed = errors.New("connection closed")
) )
// Specific variants of empty messages... // Specific variants of empty messages...
type pingMessage struct{ EmptyMessage } type pingMessage struct{ EmptyMessage }
type pongMessage struct{ EmptyMessage }
type Model interface { type Model interface {
// An index was received from the peer device // An index was received from the peer device
@@ -90,7 +82,7 @@ type Model interface {
// An index update was received from the peer device // An index update was received from the peer device
IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option)
// A request was made by the peer device // A request was made by the peer device
Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error Request(deviceID DeviceID, folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error)
// A cluster configuration message was received // A cluster configuration message was received
ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage)
// The peer device closed the connection // The peer device closed the connection
@@ -98,7 +90,6 @@ type Model interface {
} }
type Connection interface { type Connection interface {
Start()
ID() DeviceID ID() DeviceID
Name() string Name() string
Index(folder string, files []FileInfo, flags uint32, options []Option) error Index(folder string, files []FileInfo, flags uint32, options []Option) error
@@ -112,6 +103,7 @@ type rawConnection struct {
id DeviceID id DeviceID
name string name string
receiver Model receiver Model
state int
cr *countingReader cr *countingReader
cw *countingWriter cw *countingWriter
@@ -121,11 +113,11 @@ type rawConnection struct {
idxMut sync.Mutex // ensures serialization of Index calls idxMut sync.Mutex // ensures serialization of Index calls
nextID chan int nextID chan int
outbox chan hdrMsg outbox chan hdrMsg
closed chan struct{} closed chan struct{}
once sync.Once once sync.Once
pool sync.Pool
compression Compression compression Compression
rdbuf0 []byte // used & reused by readMessage rdbuf0 []byte // used & reused by readMessage
@@ -138,9 +130,8 @@ type asyncResult struct {
} }
type hdrMsg struct { type hdrMsg struct {
hdr header hdr header
msg encodable msg encodable
done chan struct{}
} }
type encodable interface { type encodable interface {
@@ -152,12 +143,8 @@ type isEofer interface {
} }
const ( const (
// PingSendInterval is how often we make sure to send a message, by pingTimeout = 30 * time.Second
// triggering pings if necessary. pingIdleTime = 60 * time.Second
PingSendInterval = 90 * time.Second
// ReceiveTimeout is the longest we'll wait for a message from the other
// side before closing the connection.
ReceiveTimeout = 300 * time.Second
) )
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection { func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection {
@@ -165,33 +152,24 @@ func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiv
cw := &countingWriter{Writer: writer} cw := &countingWriter{Writer: writer}
c := rawConnection{ c := rawConnection{
id: deviceID, id: deviceID,
name: name, name: name,
receiver: nativeModel{receiver}, receiver: nativeModel{receiver},
cr: cr, state: stateInitial,
cw: cw, cr: cr,
outbox: make(chan hdrMsg), cw: cw,
nextID: make(chan int), outbox: make(chan hdrMsg),
closed: make(chan struct{}), nextID: make(chan int),
pool: sync.Pool{ closed: make(chan struct{}),
New: func() interface{} {
return make([]byte, BlockSize)
},
},
compression: compress, compression: compress,
} }
return wireFormatConnection{&c}
}
// Start creates the goroutines for sending and receiving of messages. It must
// be called exactly once after creating a connection.
func (c *rawConnection) Start() {
go c.readerLoop() go c.readerLoop()
go c.writerLoop() go c.writerLoop()
go c.pingSender() go c.pingerLoop()
go c.pingReceiver()
go c.idGenerator() go c.idGenerator()
return wireFormatConnection{&c}
} }
func (c *rawConnection) ID() DeviceID { func (c *rawConnection) ID() DeviceID {
@@ -215,7 +193,7 @@ func (c *rawConnection) Index(folder string, idx []FileInfo, flags uint32, optio
Files: idx, Files: idx,
Flags: flags, Flags: flags,
Options: options, Options: options,
}, nil) })
c.idxMut.Unlock() c.idxMut.Unlock()
return nil return nil
} }
@@ -233,7 +211,7 @@ func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo, flags uint32,
Files: idx, Files: idx,
Flags: flags, Flags: flags,
Options: options, Options: options,
}, nil) })
c.idxMut.Unlock() c.idxMut.Unlock()
return nil return nil
} }
@@ -263,7 +241,7 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
Hash: hash, Hash: hash,
Flags: flags, Flags: flags,
Options: options, Options: options,
}, nil) })
if !ok { if !ok {
return nil, ErrClosed return nil, ErrClosed
} }
@@ -277,7 +255,7 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
// ClusterConfig send the cluster configuration message to the peer and returns any error // ClusterConfig send the cluster configuration message to the peer and returns any error
func (c *rawConnection) ClusterConfig(config ClusterConfigMessage) { func (c *rawConnection) ClusterConfig(config ClusterConfigMessage) {
c.send(-1, messageTypeClusterConfig, config, nil) c.send(-1, messageTypeClusterConfig, config)
} }
func (c *rawConnection) ping() bool { func (c *rawConnection) ping() bool {
@@ -288,7 +266,18 @@ func (c *rawConnection) ping() bool {
return false return false
} }
return c.send(id, messageTypePing, nil, nil) rc := make(chan asyncResult, 1)
c.awaitingMut.Lock()
c.awaiting[id] = rc
c.awaitingMut.Unlock()
ok := c.send(id, messageTypePing, nil)
if !ok {
return false
}
res, ok := <-rc
return ok && res.err == nil
} }
func (c *rawConnection) readerLoop() (err error) { func (c *rawConnection) readerLoop() (err error) {
@@ -296,7 +285,6 @@ func (c *rawConnection) readerLoop() (err error) {
c.close(err) c.close(err)
}() }()
state := stateInitial
for { for {
select { select {
case <-c.closed: case <-c.closed:
@@ -310,48 +298,47 @@ func (c *rawConnection) readerLoop() (err error) {
} }
switch msg := msg.(type) { switch msg := msg.(type) {
case ClusterConfigMessage:
if state != stateInitial {
return fmt.Errorf("protocol error: cluster config message in state %d", state)
}
go c.receiver.ClusterConfig(c.id, msg)
state = stateReady
case IndexMessage: case IndexMessage:
switch hdr.msgType { switch hdr.msgType {
case messageTypeIndex: case messageTypeIndex:
if state != stateReady { if c.state < stateCCRcvd {
return fmt.Errorf("protocol error: index message in state %d", state) return fmt.Errorf("protocol error: index message in state %d", c.state)
} }
c.handleIndex(msg) c.handleIndex(msg)
state = stateReady c.state = stateIdxRcvd
case messageTypeIndexUpdate: case messageTypeIndexUpdate:
if state != stateReady { if c.state < stateIdxRcvd {
return fmt.Errorf("protocol error: index update message in state %d", state) return fmt.Errorf("protocol error: index update message in state %d", c.state)
} }
c.handleIndexUpdate(msg) c.handleIndexUpdate(msg)
state = stateReady
} }
case RequestMessage: case RequestMessage:
if state != stateReady { if c.state < stateIdxRcvd {
return fmt.Errorf("protocol error: request message in state %d", state) return fmt.Errorf("protocol error: request message in state %d", c.state)
} }
// Requests are handled asynchronously // Requests are handled asynchronously
go c.handleRequest(hdr.msgID, msg) go c.handleRequest(hdr.msgID, msg)
case ResponseMessage: case ResponseMessage:
if state != stateReady { if c.state < stateIdxRcvd {
return fmt.Errorf("protocol error: response message in state %d", state) return fmt.Errorf("protocol error: response message in state %d", c.state)
} }
c.handleResponse(hdr.msgID, msg) c.handleResponse(hdr.msgID, msg)
case pingMessage: case pingMessage:
if state != stateReady { c.send(hdr.msgID, messageTypePong, pongMessage{})
return fmt.Errorf("protocol error: ping message in state %d", state)
case pongMessage:
c.handlePong(hdr.msgID)
case ClusterConfigMessage:
if c.state != stateInitial {
return fmt.Errorf("protocol error: cluster config message in state %d", c.state)
} }
// Nothing go c.receiver.ClusterConfig(c.id, msg)
c.state = stateCCRcvd
case CloseMessage: case CloseMessage:
return errors.New(msg.Reason) return errors.New(msg.Reason)
@@ -376,11 +363,8 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
hdr = decodeHeader(binary.BigEndian.Uint32(c.rdbuf0[0:4])) hdr = decodeHeader(binary.BigEndian.Uint32(c.rdbuf0[0:4]))
msglen := int(binary.BigEndian.Uint32(c.rdbuf0[4:8])) msglen := int(binary.BigEndian.Uint32(c.rdbuf0[4:8]))
l.Debugf("read header %v (msglen=%d)", hdr, msglen) if debug {
l.Debugf("read header %v (msglen=%d)", hdr, msglen)
if msglen > MaxMessageLen {
err = fmt.Errorf("message length %d exceeds maximum %d", msglen, MaxMessageLen)
return
} }
if hdr.version != 0 { if hdr.version != 0 {
@@ -398,20 +382,24 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
return return
} }
l.Debugf("read %d bytes", len(c.rdbuf0)) if debug {
l.Debugf("read %d bytes", len(c.rdbuf0))
}
msgBuf := c.rdbuf0 msgBuf := c.rdbuf0
if hdr.compression && msglen > 0 { if hdr.compression {
c.rdbuf1 = c.rdbuf1[:cap(c.rdbuf1)] c.rdbuf1 = c.rdbuf1[:cap(c.rdbuf1)]
c.rdbuf1, err = lz4.Decode(c.rdbuf1, c.rdbuf0) c.rdbuf1, err = lz4.Decode(c.rdbuf1, c.rdbuf0)
if err != nil { if err != nil {
return return
} }
msgBuf = c.rdbuf1 msgBuf = c.rdbuf1
l.Debugf("decompressed to %d bytes", len(msgBuf)) if debug {
l.Debugf("decompressed to %d bytes", len(msgBuf))
}
} }
if shouldDebug() { if debug {
if len(msgBuf) > 1024 { if len(msgBuf) > 1024 {
l.Debugf("message data:\n%s", hex.Dump(msgBuf[:1024])) l.Debugf("message data:\n%s", hex.Dump(msgBuf[:1024]))
} else { } else {
@@ -454,6 +442,9 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
case messageTypePing: case messageTypePing:
msg = pingMessage{} msg = pingMessage{}
case messageTypePong:
msg = pongMessage{}
case messageTypeClusterConfig: case messageTypeClusterConfig:
var cc ClusterConfigMessage var cc ClusterConfigMessage
err = cc.UnmarshalXDR(msgBuf) err = cc.UnmarshalXDR(msgBuf)
@@ -478,12 +469,16 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
} }
func (c *rawConnection) handleIndex(im IndexMessage) { func (c *rawConnection) handleIndex(im IndexMessage) {
l.Debugf("Index(%v, %v, %d file, flags %x, opts: %s)", c.id, im.Folder, len(im.Files), im.Flags, im.Options) if debug {
l.Debugf("Index(%v, %v, %d file, flags %x, opts: %s)", c.id, im.Folder, len(im.Files), im.Flags, im.Options)
}
c.receiver.Index(c.id, im.Folder, filterIndexMessageFiles(im.Files), im.Flags, im.Options) c.receiver.Index(c.id, im.Folder, filterIndexMessageFiles(im.Files), im.Flags, im.Options)
} }
func (c *rawConnection) handleIndexUpdate(im IndexMessage) { func (c *rawConnection) handleIndexUpdate(im IndexMessage) {
l.Debugf("queueing IndexUpdate(%v, %v, %d files, flags %x, opts: %s)", c.id, im.Folder, len(im.Files), im.Flags, im.Options) if debug {
l.Debugf("queueing IndexUpdate(%v, %v, %d files, flags %x, opts: %s)", c.id, im.Folder, len(im.Files), im.Flags, im.Options)
}
c.receiver.IndexUpdate(c.id, im.Folder, filterIndexMessageFiles(im.Files), im.Flags, im.Options) c.receiver.IndexUpdate(c.id, im.Folder, filterIndexMessageFiles(im.Files), im.Flags, im.Options)
} }
@@ -514,36 +509,12 @@ func filterIndexMessageFiles(fs []FileInfo) []FileInfo {
} }
func (c *rawConnection) handleRequest(msgID int, req RequestMessage) { func (c *rawConnection) handleRequest(msgID int, req RequestMessage) {
size := int(req.Size) data, err := c.receiver.Request(c.id, req.Folder, req.Name, int64(req.Offset), int(req.Size), req.Hash, req.Flags, req.Options)
usePool := size <= BlockSize
var buf []byte c.send(msgID, messageTypeResponse, ResponseMessage{
var done chan struct{} Data: data,
Code: errorToCode(err),
if usePool { })
buf = c.pool.Get().([]byte)[:size]
done = make(chan struct{})
} else {
buf = make([]byte, size)
}
err := c.receiver.Request(c.id, req.Folder, req.Name, int64(req.Offset), req.Hash, req.Flags, req.Options, buf)
if err != nil {
c.send(msgID, messageTypeResponse, ResponseMessage{
Data: nil,
Code: errorToCode(err),
}, done)
} else {
c.send(msgID, messageTypeResponse, ResponseMessage{
Data: buf,
Code: errorToCode(err),
}, done)
}
if usePool {
<-done
c.pool.Put(buf)
}
} }
func (c *rawConnection) handleResponse(msgID int, resp ResponseMessage) { func (c *rawConnection) handleResponse(msgID int, resp ResponseMessage) {
@@ -566,7 +537,7 @@ func (c *rawConnection) handlePong(msgID int) {
c.awaitingMut.Unlock() c.awaitingMut.Unlock()
} }
func (c *rawConnection) send(msgID int, msgType int, msg encodable, done chan struct{}) bool { func (c *rawConnection) send(msgID int, msgType int, msg encodable) bool {
if msgID < 0 { if msgID < 0 {
select { select {
case id := <-c.nextID: case id := <-c.nextID:
@@ -583,7 +554,7 @@ func (c *rawConnection) send(msgID int, msgType int, msg encodable, done chan st
} }
select { select {
case c.outbox <- hdrMsg{hdr, msg, done}: case c.outbox <- hdrMsg{hdr, msg}:
return true return true
case <-c.closed: case <-c.closed:
return false return false
@@ -602,9 +573,6 @@ func (c *rawConnection) writerLoop() {
if hm.msg != nil { if hm.msg != nil {
// Uncompressed message in uncBuf // Uncompressed message in uncBuf
uncBuf, err = hm.msg.AppendXDR(uncBuf[:0]) uncBuf, err = hm.msg.AppendXDR(uncBuf[:0])
if hm.done != nil {
close(hm.done)
}
if err != nil { if err != nil {
c.close(err) c.close(err)
return return
@@ -633,7 +601,9 @@ func (c *rawConnection) writerLoop() {
binary.BigEndian.PutUint32(msgBuf[4:8], uint32(len(tempBuf))) binary.BigEndian.PutUint32(msgBuf[4:8], uint32(len(tempBuf)))
msgBuf = msgBuf[0 : len(tempBuf)+8] msgBuf = msgBuf[0 : len(tempBuf)+8]
l.Debugf("write compressed message; %v (len=%d)", hm.hdr, len(tempBuf)) if debug {
l.Debugf("write compressed message; %v (len=%d)", hm.hdr, len(tempBuf))
}
} else { } else {
// No point in compressing very short messages // No point in compressing very short messages
hm.hdr.compression = false hm.hdr.compression = false
@@ -647,10 +617,14 @@ func (c *rawConnection) writerLoop() {
msgBuf = msgBuf[0 : len(uncBuf)+8] msgBuf = msgBuf[0 : len(uncBuf)+8]
copy(msgBuf[8:], uncBuf) copy(msgBuf[8:], uncBuf)
l.Debugf("write uncompressed message; %v (len=%d)", hm.hdr, len(uncBuf)) if debug {
l.Debugf("write uncompressed message; %v (len=%d)", hm.hdr, len(uncBuf))
}
} }
} else { } else {
l.Debugf("write empty message; %v", hm.hdr) if debug {
l.Debugf("write empty message; %v", hm.hdr)
}
binary.BigEndian.PutUint32(msgBuf[4:8], 0) binary.BigEndian.PutUint32(msgBuf[4:8], 0)
msgBuf = msgBuf[:8] msgBuf = msgBuf[:8]
} }
@@ -660,7 +634,9 @@ func (c *rawConnection) writerLoop() {
if err == nil { if err == nil {
var n int var n int
n, err = c.cw.Write(msgBuf) n, err = c.cw.Write(msgBuf)
l.Debugf("wrote %d bytes on the wire", n) if debug {
l.Debugf("wrote %d bytes on the wire", n)
}
} }
if err != nil { if err != nil {
c.close(err) c.close(err)
@@ -701,48 +677,43 @@ func (c *rawConnection) idGenerator() {
} }
} }
// The pingSender makes sure that we've sent a message within the last func (c *rawConnection) pingerLoop() {
// PingSendInterval. If we already have something sent in the last var rc = make(chan bool, 1)
// PingSendInterval/2, we do nothing. Otherwise we send a ping message. This ticker := time.Tick(pingIdleTime / 2)
// results in an effecting ping interval of somewhere between
// PingSendInterval/2 and PingSendInterval.
func (c *rawConnection) pingSender() {
ticker := time.Tick(PingSendInterval / 2)
for { for {
select { select {
case <-ticker: case <-ticker:
d := time.Since(c.cw.Last()) if d := time.Since(c.cr.Last()); d < pingIdleTime {
if d < PingSendInterval/2 { if debug {
l.Debugln(c.id, "ping skipped after wr", d) l.Debugln(c.id, "ping skipped after rd", d)
}
continue continue
} }
if d := time.Since(c.cw.Last()); d < pingIdleTime {
l.Debugln(c.id, "ping -> after", d) if debug {
c.ping() l.Debugln(c.id, "ping skipped after wr", d)
}
case <-c.closed: continue
return }
} go func() {
} if debug {
} l.Debugln(c.id, "ping ->")
}
// The pingReciever checks that we've received a message (any message will do, rc <- c.ping()
// but we expect pings in the absence of other messages) within the last }()
// ReceiveTimeout. If not, we close the connection with an ErrTimeout. select {
func (c *rawConnection) pingReceiver() { case ok := <-rc:
ticker := time.Tick(ReceiveTimeout / 2) if debug {
l.Debugln(c.id, "<- pong")
for { }
select { if !ok {
case <-ticker: c.close(fmt.Errorf("ping failure"))
d := time.Since(c.cr.Last()) }
if d > ReceiveTimeout { case <-time.After(pingTimeout):
l.Debugln(c.id, "ping timeout", d) c.close(fmt.Errorf("ping timeout"))
c.close(ErrTimeout) case <-c.closed:
return
} }
l.Debugln(c.id, "last read within", d)
case <-c.closed: case <-c.closed:
return return
@@ -6,7 +6,7 @@ import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"flag" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@@ -20,21 +20,11 @@ import (
) )
var ( var (
c0ID = NewDeviceID([]byte{1}) c0ID = NewDeviceID([]byte{1})
c1ID = NewDeviceID([]byte{2}) c1ID = NewDeviceID([]byte{2})
quickCfg = &quick.Config{}
) )
func TestMain(m *testing.M) {
flag.Parse()
if flag.Lookup("test.short").Value.String() != "false" {
quickCfg.MaxCount = 10
}
os.Exit(m.Run())
}
func TestHeaderFunctions(t *testing.T) { func TestHeaderFunctions(t *testing.T) {
t.Parallel()
f := func(ver, id, typ int) bool { f := func(ver, id, typ int) bool {
ver = int(uint(ver) % 16) ver = int(uint(ver) % 16)
id = int(uint(id) % 4096) id = int(uint(id) % 4096)
@@ -49,7 +39,6 @@ func TestHeaderFunctions(t *testing.T) {
} }
func TestHeaderLayout(t *testing.T) { func TestHeaderLayout(t *testing.T) {
t.Parallel()
var e, a uint32 var e, a uint32
// Version are the first four bits // Version are the first four bits
@@ -75,16 +64,11 @@ func TestHeaderLayout(t *testing.T) {
} }
func TestPing(t *testing.T) { func TestPing(t *testing.T) {
t.Parallel()
ar, aw := io.Pipe() ar, aw := io.Pipe()
br, bw := io.Pipe() br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, newTestModel(), "name", CompressAlways).(wireFormatConnection).next.(*rawConnection) c0 := NewConnection(c0ID, ar, bw, nil, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
c0.Start() c1 := NewConnection(c1ID, br, aw, nil, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
c1 := NewConnection(c1ID, br, aw, newTestModel(), "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
c1.Start()
c0.ClusterConfig(ClusterConfigMessage{})
c1.ClusterConfig(ClusterConfigMessage{})
if ok := c0.ping(); !ok { if ok := c0.ping(); !ok {
t.Error("c0 ping failed") t.Error("c0 ping failed")
@@ -94,8 +78,89 @@ func TestPing(t *testing.T) {
} }
} }
func TestPingErr(t *testing.T) {
e := errors.New("something broke")
for i := 0; i < 16; i++ {
for j := 0; j < 16; j++ {
m0 := newTestModel()
m1 := newTestModel()
ar, aw := io.Pipe()
br, bw := io.Pipe()
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
c0 := NewConnection(c0ID, ar, ebw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, eaw, m1, "name", CompressAlways)
res := c0.ping()
if (i < 8 || j < 8) && res {
t.Errorf("Unexpected ping success; i=%d, j=%d", i, j)
} else if (i >= 12 && j >= 12) && !res {
t.Errorf("Unexpected ping fail; i=%d, j=%d", i, j)
}
}
}
}
// func TestRequestResponseErr(t *testing.T) {
// e := errors.New("something broke")
// var pass bool
// for i := 0; i < 48; i++ {
// for j := 0; j < 38; j++ {
// m0 := newTestModel()
// m0.data = []byte("response data")
// m1 := newTestModel()
// ar, aw := io.Pipe()
// br, bw := io.Pipe()
// eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
// ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
// NewConnection(c0ID, ar, ebw, m0, nil)
// c1 := NewConnection(c1ID, br, eaw, m1, nil).(wireFormatConnection).next.(*rawConnection)
// d, err := c1.Request("default", "tn", 1234, 5678)
// if err == e || err == ErrClosed {
// t.Logf("Error at %d+%d bytes", i, j)
// if !m1.isClosed() {
// t.Fatal("c1 not closed")
// }
// if !m0.isClosed() {
// t.Fatal("c0 not closed")
// }
// continue
// }
// if err != nil {
// t.Fatal(err)
// }
// if string(d) != "response data" {
// t.Fatalf("Incorrect response data %q", string(d))
// }
// if m0.folder != "default" {
// t.Fatalf("Incorrect folder %q", m0.folder)
// }
// if m0.name != "tn" {
// t.Fatalf("Incorrect name %q", m0.name)
// }
// if m0.offset != 1234 {
// t.Fatalf("Incorrect offset %d", m0.offset)
// }
// if m0.size != 5678 {
// t.Fatalf("Incorrect size %d", m0.size)
// }
// t.Logf("Pass at %d+%d bytes", i, j)
// pass = true
// }
// }
// if !pass {
// t.Fatal("Never passed")
// }
// }
func TestVersionErr(t *testing.T) { func TestVersionErr(t *testing.T) {
t.Parallel()
m0 := newTestModel() m0 := newTestModel()
m1 := newTestModel() m1 := newTestModel()
@@ -103,11 +168,7 @@ func TestVersionErr(t *testing.T) {
br, bw := io.Pipe() br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection) c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
c0.Start() NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
c1.Start()
c0.ClusterConfig(ClusterConfigMessage{})
c1.ClusterConfig(ClusterConfigMessage{})
w := xdr.NewWriter(c0.cw) w := xdr.NewWriter(c0.cw)
w.WriteUint32(encodeHeader(header{ w.WriteUint32(encodeHeader(header{
@@ -123,7 +184,6 @@ func TestVersionErr(t *testing.T) {
} }
func TestTypeErr(t *testing.T) { func TestTypeErr(t *testing.T) {
t.Parallel()
m0 := newTestModel() m0 := newTestModel()
m1 := newTestModel() m1 := newTestModel()
@@ -131,11 +191,7 @@ func TestTypeErr(t *testing.T) {
br, bw := io.Pipe() br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection) c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
c0.Start() NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
c1.Start()
c0.ClusterConfig(ClusterConfigMessage{})
c1.ClusterConfig(ClusterConfigMessage{})
w := xdr.NewWriter(c0.cw) w := xdr.NewWriter(c0.cw)
w.WriteUint32(encodeHeader(header{ w.WriteUint32(encodeHeader(header{
@@ -151,7 +207,6 @@ func TestTypeErr(t *testing.T) {
} }
func TestClose(t *testing.T) { func TestClose(t *testing.T) {
t.Parallel()
m0 := newTestModel() m0 := newTestModel()
m1 := newTestModel() m1 := newTestModel()
@@ -159,11 +214,7 @@ func TestClose(t *testing.T) {
br, bw := io.Pipe() br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection) c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
c0.Start() NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
c1.Start()
c0.ClusterConfig(ClusterConfigMessage{})
c1.ClusterConfig(ClusterConfigMessage{})
c0.close(nil) c0.close(nil)
@@ -187,9 +238,10 @@ func TestClose(t *testing.T) {
} }
func TestElementSizeExceededNested(t *testing.T) { func TestElementSizeExceededNested(t *testing.T) {
t.Parallel()
m := ClusterConfigMessage{ m := ClusterConfigMessage{
ClientName: "longstringlongstringlongstringinglongstringlongstringlonlongstringlongstringlon", Folders: []Folder{
{ID: "longstringlongstringlongstringinglongstringlongstringlonlongstringlongstringlon"},
},
} }
_, err := m.EncodeXDR(ioutil.Discard) _, err := m.EncodeXDR(ioutil.Discard)
if err == nil { if err == nil {
@@ -198,14 +250,17 @@ func TestElementSizeExceededNested(t *testing.T) {
} }
func TestMarshalIndexMessage(t *testing.T) { func TestMarshalIndexMessage(t *testing.T) {
t.Parallel() var quickCfg = &quick.Config{MaxCountScale: 10}
if testing.Short() {
quickCfg = nil
}
f := func(m1 IndexMessage) bool { f := func(m1 IndexMessage) bool {
for i, f := range m1.Files { for _, f := range m1.Files {
m1.Files[i].CachedSize = 0 for i := range f.Blocks {
for j := range f.Blocks { f.Blocks[i].Offset = 0
f.Blocks[j].Offset = 0 if len(f.Blocks[i].Hash) == 0 {
if len(f.Blocks[j].Hash) == 0 { f.Blocks[i].Hash = nil
f.Blocks[j].Hash = nil
} }
} }
} }
@@ -219,7 +274,11 @@ func TestMarshalIndexMessage(t *testing.T) {
} }
func TestMarshalRequestMessage(t *testing.T) { func TestMarshalRequestMessage(t *testing.T) {
t.Parallel() var quickCfg = &quick.Config{MaxCountScale: 10}
if testing.Short() {
quickCfg = nil
}
f := func(m1 RequestMessage) bool { f := func(m1 RequestMessage) bool {
return testMarshal(t, "request", &m1, &RequestMessage{}) return testMarshal(t, "request", &m1, &RequestMessage{})
} }
@@ -230,7 +289,11 @@ func TestMarshalRequestMessage(t *testing.T) {
} }
func TestMarshalResponseMessage(t *testing.T) { func TestMarshalResponseMessage(t *testing.T) {
t.Parallel() var quickCfg = &quick.Config{MaxCountScale: 10}
if testing.Short() {
quickCfg = nil
}
f := func(m1 ResponseMessage) bool { f := func(m1 ResponseMessage) bool {
if len(m1.Data) == 0 { if len(m1.Data) == 0 {
m1.Data = nil m1.Data = nil
@@ -244,7 +307,11 @@ func TestMarshalResponseMessage(t *testing.T) {
} }
func TestMarshalClusterConfigMessage(t *testing.T) { func TestMarshalClusterConfigMessage(t *testing.T) {
t.Parallel() var quickCfg = &quick.Config{MaxCountScale: 10}
if testing.Short() {
quickCfg = nil
}
f := func(m1 ClusterConfigMessage) bool { f := func(m1 ClusterConfigMessage) bool {
return testMarshal(t, "clusterconfig", &m1, &ClusterConfigMessage{}) return testMarshal(t, "clusterconfig", &m1, &ClusterConfigMessage{})
} }
@@ -255,7 +322,11 @@ func TestMarshalClusterConfigMessage(t *testing.T) {
} }
func TestMarshalCloseMessage(t *testing.T) { func TestMarshalCloseMessage(t *testing.T) {
t.Parallel() var quickCfg = &quick.Config{MaxCountScale: 10}
if testing.Short() {
quickCfg = nil
}
f := func(m1 CloseMessage) bool { f := func(m1 CloseMessage) bool {
return testMarshal(t, "close", &m1, &CloseMessage{}) return testMarshal(t, "close", &m1, &CloseMessage{})
} }
@@ -4,7 +4,7 @@ package protocol
// The Vector type represents a version vector. The zero value is a usable // The Vector type represents a version vector. The zero value is a usable
// version vector. The vector has slice semantics and some operations on it // version vector. The vector has slice semantics and some operations on it
// are "append-like" in that they may return the same vector modified, or v // are "append-like" in that they may return the same vector modified, or a
// new allocated Vector with the modified contents. // new allocated Vector with the modified contents.
type Vector []Counter type Vector []Counter
@@ -33,43 +33,43 @@ func (v Vector) Update(ID uint64) Vector {
return nv return nv
} }
} }
// Append a new index // Append a new new index
return append(v, Counter{ID, 1}) return append(v, Counter{ID, 1})
} }
// Merge returns the vector containing the maximum indexes from v and b. If it // Merge returns the vector containing the maximum indexes from a and b. If it
// is possible, the vector v is updated and returned. If it is not, a copy // is possible, the vector a is updated and returned. If it is not, a copy
// will be created, updated and returned. // will be created, updated and returned.
func (v Vector) Merge(b Vector) Vector { func (a Vector) Merge(b Vector) Vector {
var vi, bi int var ai, bi int
for bi < len(b) { for bi < len(b) {
if vi == len(v) { if ai == len(a) {
// We've reach the end of v, all that remains are appends // We've reach the end of a, all that remains are appends
return append(v, b[bi:]...) return append(a, b[bi:]...)
} }
if v[vi].ID > b[bi].ID { if a[ai].ID > b[bi].ID {
// The index from b should be inserted here // The index from b should be inserted here
n := make(Vector, len(v)+1) n := make(Vector, len(a)+1)
copy(n, v[:vi]) copy(n, a[:ai])
n[vi] = b[bi] n[ai] = b[bi]
copy(n[vi+1:], v[vi:]) copy(n[ai+1:], a[ai:])
v = n a = n
} }
if v[vi].ID == b[bi].ID { if a[ai].ID == b[bi].ID {
if val := b[bi].Value; val > v[vi].Value { if v := b[bi].Value; v > a[ai].Value {
v[vi].Value = val a[ai].Value = v
} }
} }
if bi < len(b) && v[vi].ID == b[bi].ID { if bi < len(b) && a[ai].ID == b[bi].ID {
bi++ bi++
} }
vi++ ai++
} }
return v return a
} }
// Copy returns an identical vector that is not shared with v. // Copy returns an identical vector that is not shared with v.
@@ -80,27 +80,27 @@ func (v Vector) Copy() Vector {
} }
// Equal returns true when the two vectors are equivalent. // Equal returns true when the two vectors are equivalent.
func (v Vector) Equal(b Vector) bool { func (a Vector) Equal(b Vector) bool {
return v.Compare(b) == Equal return a.Compare(b) == Equal
} }
// LesserEqual returns true when the two vectors are equivalent or v is Lesser // LesserEqual returns true when the two vectors are equivalent or a is Lesser
// than b. // than b.
func (v Vector) LesserEqual(b Vector) bool { func (a Vector) LesserEqual(b Vector) bool {
comp := v.Compare(b) comp := a.Compare(b)
return comp == Lesser || comp == Equal return comp == Lesser || comp == Equal
} }
// GreaterEqual returns true when the two vectors are equivalent or v is Greater // LesserEqual returns true when the two vectors are equivalent or a is Greater
// than b. // than b.
func (v Vector) GreaterEqual(b Vector) bool { func (a Vector) GreaterEqual(b Vector) bool {
comp := v.Compare(b) comp := a.Compare(b)
return comp == Greater || comp == Equal return comp == Greater || comp == Equal
} }
// Concurrent returns true when the two vectors are concrurrent. // Concurrent returns true when the two vectors are concrurrent.
func (v Vector) Concurrent(b Vector) bool { func (a Vector) Concurrent(b Vector) bool {
comp := v.Compare(b) comp := a.Compare(b)
return comp == ConcurrentGreater || comp == ConcurrentLesser return comp == ConcurrentGreater || comp == ConcurrentLesser
} }
@@ -123,12 +123,12 @@ func TestMerge(t *testing.T) {
func TestCounterValue(t *testing.T) { func TestCounterValue(t *testing.T) {
v0 := Vector{Counter{42, 1}, Counter{64, 5}} v0 := Vector{Counter{42, 1}, Counter{64, 5}}
if v0.Counter(42) != 1 { if v0.Counter(42) != 1 {
t.Errorf("Counter error, %d != %d", v0.Counter(42), 1) t.Error("Counter error, %d != %d", v0.Counter(42), 1)
} }
if v0.Counter(64) != 5 { if v0.Counter(64) != 5 {
t.Errorf("Counter error, %d != %d", v0.Counter(64), 5) t.Error("Counter error, %d != %d", v0.Counter(64), 5)
} }
if v0.Counter(72) != 0 { if v0.Counter(72) != 0 {
t.Errorf("Counter error, %d != %d", v0.Counter(72), 0) t.Error("Counter error, %d != %d", v0.Counter(72), 0)
} }
} }
@@ -2,8 +2,6 @@
package protocol package protocol
import "github.com/calmh/xdr"
// This stuff is hacked up manually because genxdr doesn't support 'type // This stuff is hacked up manually because genxdr doesn't support 'type
// Vector []Counter' declarations and it was tricky when I tried to add it... // Vector []Counter' declarations and it was tricky when I tried to add it...
@@ -30,9 +28,6 @@ func (v Vector) EncodeXDRInto(w xdrWriter) (int, error) {
// DecodeXDRFrom decodes the XDR objects from the given reader into itself. // DecodeXDRFrom decodes the XDR objects from the given reader into itself.
func (v *Vector) DecodeXDRFrom(r xdrReader) error { func (v *Vector) DecodeXDRFrom(r xdrReader) error {
l := int(r.ReadUint32()) l := int(r.ReadUint32())
if l > 1e6 {
return xdr.ElementSizeExceeded("number of counters", l, 1e6)
}
n := make(Vector, l) n := make(Vector, l)
for i := range n { for i := range n {
n[i].ID = r.ReadUint64() n[i].ID = r.ReadUint64()
@@ -12,10 +12,6 @@ type wireFormatConnection struct {
next Connection next Connection
} }
func (c wireFormatConnection) Start() {
c.next.Start()
}
func (c wireFormatConnection) ID() DeviceID { func (c wireFormatConnection) ID() DeviceID {
return c.next.ID() return c.next.ID()
} }
+148 -274
View File
@@ -63,14 +63,13 @@ type DB struct {
journalAckC chan error journalAckC chan error
// Compaction. // Compaction.
tcompCmdC chan cCmd tcompCmdC chan cCmd
tcompPauseC chan chan<- struct{} tcompPauseC chan chan<- struct{}
mcompCmdC chan cCmd mcompCmdC chan cCmd
compErrC chan error compErrC chan error
compPerErrC chan error compPerErrC chan error
compErrSetC chan error compErrSetC chan error
compWriteLocking bool compStats []cStats
compStats []cStats
// Close. // Close.
closeW sync.WaitGroup closeW sync.WaitGroup
@@ -109,44 +108,28 @@ func openDB(s *session) (*DB, error) {
closeC: make(chan struct{}), closeC: make(chan struct{}),
} }
// Read-only mode. if err := db.recoverJournal(); err != nil {
readOnly := s.o.GetReadOnly() return nil, err
}
if readOnly { // Remove any obsolete files.
// Recover journals (read-only mode). if err := db.checkAndCleanFiles(); err != nil {
if err := db.recoverJournalRO(); err != nil { // Close journal.
return nil, err if db.journal != nil {
db.journal.Close()
db.journalWriter.Close()
} }
} else { return nil, err
// Recover journals.
if err := db.recoverJournal(); err != nil {
return nil, err
}
// Remove any obsolete files.
if err := db.checkAndCleanFiles(); err != nil {
// Close journal.
if db.journal != nil {
db.journal.Close()
db.journalWriter.Close()
}
return nil, err
}
} }
// Doesn't need to be included in the wait group. // Doesn't need to be included in the wait group.
go db.compactionError() go db.compactionError()
go db.mpoolDrain() go db.mpoolDrain()
if readOnly { db.closeW.Add(3)
db.SetReadOnly() go db.tCompaction()
} else { go db.mCompaction()
db.closeW.Add(3) go db.jWriter()
go db.tCompaction()
go db.mCompaction()
go db.jWriter()
}
s.logf("db@open done T·%v", time.Since(start)) s.logf("db@open done T·%v", time.Since(start))
@@ -291,9 +274,8 @@ func recoverTable(s *session, o *opt.Options) error {
// We will drop corrupted table. // We will drop corrupted table.
strict = o.GetStrict(opt.StrictRecovery) strict = o.GetStrict(opt.StrictRecovery)
noSync = o.GetNoSync()
rec = &sessionRecord{} rec = &sessionRecord{numLevel: o.GetNumLevel()}
bpool = util.NewBufferPool(o.GetBlockSize() + 5) bpool = util.NewBufferPool(o.GetBlockSize() + 5)
) )
buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) { buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) {
@@ -329,11 +311,9 @@ func recoverTable(s *session, o *opt.Options) error {
if err != nil { if err != nil {
return return
} }
if !noSync { err = writer.Sync()
err = writer.Sync() if err != nil {
if err != nil { return
return
}
} }
size = int64(tw.BytesLen()) size = int64(tw.BytesLen())
return return
@@ -470,136 +450,132 @@ func recoverTable(s *session, o *opt.Options) error {
} }
func (db *DB) recoverJournal() error { func (db *DB) recoverJournal() error {
// Get all journals and sort it by file number. // Get all tables and sort it by file number.
allJournalFiles, err := db.s.getFiles(storage.TypeJournal) journalFiles_, err := db.s.getFiles(storage.TypeJournal)
if err != nil { if err != nil {
return err return err
} }
files(allJournalFiles).sort() journalFiles := files(journalFiles_)
journalFiles.sort()
// Journals that will be recovered. // Discard older journal.
var recJournalFiles []storage.File prev := -1
for _, jf := range allJournalFiles { for i, file := range journalFiles {
if jf.Num() >= db.s.stJournalNum || jf.Num() == db.s.stPrevJournalNum { if file.Num() >= db.s.stJournalNum {
recJournalFiles = append(recJournalFiles, jf) if prev >= 0 {
i--
journalFiles[i] = journalFiles[prev]
}
journalFiles = journalFiles[i:]
break
} else if file.Num() == db.s.stPrevJournalNum {
prev = i
} }
} }
var ( var jr *journal.Reader
of storage.File // Obsolete file. var of storage.File
rec = &sessionRecord{} var mem *memdb.DB
) batch := new(Batch)
cm := newCMem(db.s)
buf := new(util.Buffer)
// Options.
strict := db.s.o.GetStrict(opt.StrictJournal)
checksum := db.s.o.GetStrict(opt.StrictJournalChecksum)
writeBuffer := db.s.o.GetWriteBuffer()
recoverJournal := func(file storage.File) error {
db.logf("journal@recovery recovering @%d", file.Num())
reader, err := file.Open()
if err != nil {
return err
}
defer reader.Close()
// Recover journals. // Create/reset journal reader instance.
if len(recJournalFiles) > 0 { if jr == nil {
db.logf("journal@recovery F·%d", len(recJournalFiles)) jr = journal.NewReader(reader, dropper{db.s, file}, strict, checksum)
} else {
// Mark file number as used. jr.Reset(reader, dropper{db.s, file}, strict, checksum)
db.s.markFileNum(recJournalFiles[len(recJournalFiles)-1].Num())
var (
// Options.
strict = db.s.o.GetStrict(opt.StrictJournal)
checksum = db.s.o.GetStrict(opt.StrictJournalChecksum)
writeBuffer = db.s.o.GetWriteBuffer()
jr *journal.Reader
mdb = memdb.New(db.s.icmp, writeBuffer)
buf = &util.Buffer{}
batch = &Batch{}
)
for _, jf := range recJournalFiles {
db.logf("journal@recovery recovering @%d", jf.Num())
fr, err := jf.Open()
if err != nil {
return err
}
// Create or reset journal reader instance.
if jr == nil {
jr = journal.NewReader(fr, dropper{db.s, jf}, strict, checksum)
} else {
jr.Reset(fr, dropper{db.s, jf}, strict, checksum)
}
// Flush memdb and remove obsolete journal file.
if of != nil {
if mdb.Len() > 0 {
if _, err := db.s.flushMemdb(rec, mdb, -1); err != nil {
fr.Close()
return err
}
}
rec.setJournalNum(jf.Num())
rec.setSeqNum(db.seq)
if err := db.s.commit(rec); err != nil {
fr.Close()
return err
}
rec.resetAddedTables()
of.Remove()
of = nil
}
// Replay journal to memdb.
mdb.Reset()
for {
r, err := jr.Next()
if err != nil {
if err == io.EOF {
break
}
fr.Close()
return errors.SetFile(err, jf)
}
buf.Reset()
if _, err := buf.ReadFrom(r); err != nil {
if err == io.ErrUnexpectedEOF {
// This is error returned due to corruption, with strict == false.
continue
}
fr.Close()
return errors.SetFile(err, jf)
}
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mdb); err != nil {
if !strict && errors.IsCorrupted(err) {
db.s.logf("journal error: %v (skipped)", err)
// We won't apply sequence number as it might be corrupted.
continue
}
fr.Close()
return errors.SetFile(err, jf)
}
// Save sequence number.
db.seq = batch.seq + uint64(batch.Len())
// Flush it if large enough.
if mdb.Size() >= writeBuffer {
if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil {
fr.Close()
return err
}
mdb.Reset()
}
}
fr.Close()
of = jf
} }
// Flush the last memdb. // Flush memdb and remove obsolete journal file.
if mdb.Len() > 0 { if of != nil {
if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil { if mem.Len() > 0 {
if err := cm.flush(mem, 0); err != nil {
return err
}
}
if err := cm.commit(file.Num(), db.seq); err != nil {
return err
}
cm.reset()
of.Remove()
of = nil
}
// Replay journal to memdb.
mem.Reset()
for {
r, err := jr.Next()
if err != nil {
if err == io.EOF {
break
}
return errors.SetFile(err, file)
}
buf.Reset()
if _, err := buf.ReadFrom(r); err != nil {
if err == io.ErrUnexpectedEOF {
// This is error returned due to corruption, with strict == false.
continue
} else {
return errors.SetFile(err, file)
}
}
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mem); err != nil {
if strict || !errors.IsCorrupted(err) {
return errors.SetFile(err, file)
} else {
db.s.logf("journal error: %v (skipped)", err)
// We won't apply sequence number as it might be corrupted.
continue
}
}
// Save sequence number.
db.seq = batch.seq + uint64(batch.Len())
// Flush it if large enough.
if mem.Size() >= writeBuffer {
if err := cm.flush(mem, 0); err != nil {
return err
}
mem.Reset()
}
}
of = file
return nil
}
// Recover all journals.
if len(journalFiles) > 0 {
db.logf("journal@recovery F·%d", len(journalFiles))
// Mark file number as used.
db.s.markFileNum(journalFiles[len(journalFiles)-1].Num())
mem = memdb.New(db.s.icmp, writeBuffer)
for _, file := range journalFiles {
if err := recoverJournal(file); err != nil {
return err
}
}
// Flush the last journal.
if mem.Len() > 0 {
if err := cm.flush(mem, 0); err != nil {
return err return err
} }
} }
@@ -611,10 +587,8 @@ func (db *DB) recoverJournal() error {
} }
// Commit. // Commit.
rec.setJournalNum(db.journalFile.Num()) if err := cm.commit(db.journalFile.Num(), db.seq); err != nil {
rec.setSeqNum(db.seq) // Close journal.
if err := db.s.commit(rec); err != nil {
// Close journal on error.
if db.journal != nil { if db.journal != nil {
db.journal.Close() db.journal.Close()
db.journalWriter.Close() db.journalWriter.Close()
@@ -630,103 +604,6 @@ func (db *DB) recoverJournal() error {
return nil return nil
} }
func (db *DB) recoverJournalRO() error {
// Get all journals and sort it by file number.
allJournalFiles, err := db.s.getFiles(storage.TypeJournal)
if err != nil {
return err
}
files(allJournalFiles).sort()
// Journals that will be recovered.
var recJournalFiles []storage.File
for _, jf := range allJournalFiles {
if jf.Num() >= db.s.stJournalNum || jf.Num() == db.s.stPrevJournalNum {
recJournalFiles = append(recJournalFiles, jf)
}
}
var (
// Options.
strict = db.s.o.GetStrict(opt.StrictJournal)
checksum = db.s.o.GetStrict(opt.StrictJournalChecksum)
writeBuffer = db.s.o.GetWriteBuffer()
mdb = memdb.New(db.s.icmp, writeBuffer)
)
// Recover journals.
if len(recJournalFiles) > 0 {
db.logf("journal@recovery RO·Mode F·%d", len(recJournalFiles))
var (
jr *journal.Reader
buf = &util.Buffer{}
batch = &Batch{}
)
for _, jf := range recJournalFiles {
db.logf("journal@recovery recovering @%d", jf.Num())
fr, err := jf.Open()
if err != nil {
return err
}
// Create or reset journal reader instance.
if jr == nil {
jr = journal.NewReader(fr, dropper{db.s, jf}, strict, checksum)
} else {
jr.Reset(fr, dropper{db.s, jf}, strict, checksum)
}
// Replay journal to memdb.
for {
r, err := jr.Next()
if err != nil {
if err == io.EOF {
break
}
fr.Close()
return errors.SetFile(err, jf)
}
buf.Reset()
if _, err := buf.ReadFrom(r); err != nil {
if err == io.ErrUnexpectedEOF {
// This is error returned due to corruption, with strict == false.
continue
}
fr.Close()
return errors.SetFile(err, jf)
}
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mdb); err != nil {
if !strict && errors.IsCorrupted(err) {
db.s.logf("journal error: %v (skipped)", err)
// We won't apply sequence number as it might be corrupted.
continue
}
fr.Close()
return errors.SetFile(err, jf)
}
// Save sequence number.
db.seq = batch.seq + uint64(batch.Len())
}
fr.Close()
}
}
// Set memDB.
db.mem = &memDB{db: db, DB: mdb, ref: 1}
return nil
}
func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) { func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) {
ikey := newIkey(key, seq, ktSeek) ikey := newIkey(key, seq, ktSeek)
@@ -737,7 +614,7 @@ func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, er
} }
defer m.decref() defer m.decref()
mk, mv, me := m.Find(ikey) mk, mv, me := m.mdb.Find(ikey)
if me == nil { if me == nil {
ukey, _, kt, kerr := parseIkey(mk) ukey, _, kt, kerr := parseIkey(mk)
if kerr != nil { if kerr != nil {
@@ -775,7 +652,7 @@ func (db *DB) has(key []byte, seq uint64, ro *opt.ReadOptions) (ret bool, err er
} }
defer m.decref() defer m.decref()
mk, _, me := m.Find(ikey) mk, _, me := m.mdb.Find(ikey)
if me == nil { if me == nil {
ukey, _, kt, kerr := parseIkey(mk) ukey, _, kt, kerr := parseIkey(mk)
if kerr != nil { if kerr != nil {
@@ -907,7 +784,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
const prefix = "leveldb." const prefix = "leveldb."
if !strings.HasPrefix(name, prefix) { if !strings.HasPrefix(name, prefix) {
return "", ErrNotFound return "", errors.New("leveldb: GetProperty: unknown property: " + name)
} }
p := name[len(prefix):] p := name[len(prefix):]
@@ -921,7 +798,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
var rest string var rest string
n, _ := fmt.Sscanf(p[len(numFilesPrefix):], "%d%s", &level, &rest) n, _ := fmt.Sscanf(p[len(numFilesPrefix):], "%d%s", &level, &rest)
if n != 1 || int(level) >= db.s.o.GetNumLevel() { if n != 1 || int(level) >= db.s.o.GetNumLevel() {
err = ErrNotFound err = errors.New("leveldb: GetProperty: invalid property: " + name)
} else { } else {
value = fmt.Sprint(v.tLen(int(level))) value = fmt.Sprint(v.tLen(int(level)))
} }
@@ -960,7 +837,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
case p == "aliveiters": case p == "aliveiters":
value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveIters)) value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveIters))
default: default:
err = ErrNotFound err = errors.New("leveldb: GetProperty: unknown property: " + name)
} }
return return
@@ -1023,9 +900,6 @@ func (db *DB) Close() error {
var err error var err error
select { select {
case err = <-db.compErrC: case err = <-db.compErrC:
if err == ErrReadOnly {
err = nil
}
default: default:
} }
+79 -35
View File
@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/memdb"
"github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/opt"
) )
@@ -61,8 +62,58 @@ func (p *cStatsStaging) stopTimer() {
} }
} }
type cMem struct {
s *session
level int
rec *sessionRecord
}
func newCMem(s *session) *cMem {
return &cMem{s: s, rec: &sessionRecord{numLevel: s.o.GetNumLevel()}}
}
func (c *cMem) flush(mem *memdb.DB, level int) error {
s := c.s
// Write memdb to table.
iter := mem.NewIterator(nil)
defer iter.Release()
t, n, err := s.tops.createFrom(iter)
if err != nil {
return err
}
// Pick level.
if level < 0 {
v := s.version()
level = v.pickLevel(t.imin.ukey(), t.imax.ukey())
v.release()
}
c.rec.addTableFile(level, t)
s.logf("mem@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.imin, t.imax)
c.level = level
return nil
}
func (c *cMem) reset() {
c.rec = &sessionRecord{numLevel: c.s.o.GetNumLevel()}
}
func (c *cMem) commit(journal, seq uint64) error {
c.rec.setJournalNum(journal)
c.rec.setSeqNum(seq)
// Commit changes.
return c.s.commit(c.rec)
}
func (db *DB) compactionError() { func (db *DB) compactionError() {
var err error var (
err error
wlocked bool
)
noerr: noerr:
// No error. // No error.
for { for {
@@ -70,7 +121,7 @@ noerr:
case err = <-db.compErrSetC: case err = <-db.compErrSetC:
switch { switch {
case err == nil: case err == nil:
case err == ErrReadOnly, errors.IsCorrupted(err): case errors.IsCorrupted(err):
goto hasperr goto hasperr
default: default:
goto haserr goto haserr
@@ -88,7 +139,7 @@ haserr:
switch { switch {
case err == nil: case err == nil:
goto noerr goto noerr
case err == ErrReadOnly, errors.IsCorrupted(err): case errors.IsCorrupted(err):
goto hasperr goto hasperr
default: default:
} }
@@ -104,9 +155,9 @@ hasperr:
case db.compPerErrC <- err: case db.compPerErrC <- err:
case db.writeLockC <- struct{}{}: case db.writeLockC <- struct{}{}:
// Hold write lock, so that write won't pass-through. // Hold write lock, so that write won't pass-through.
db.compWriteLocking = true wlocked = true
case _, _ = <-db.closeC: case _, _ = <-db.closeC:
if db.compWriteLocking { if wlocked {
// We should release the lock or Close will hang. // We should release the lock or Close will hang.
<-db.writeLockC <-db.writeLockC
} }
@@ -236,18 +287,21 @@ func (db *DB) compactionExitTransact() {
} }
func (db *DB) memCompaction() { func (db *DB) memCompaction() {
mdb := db.getFrozenMem() mem := db.getFrozenMem()
if mdb == nil { if mem == nil {
return return
} }
defer mdb.decref() defer mem.decref()
db.logf("memdb@flush N·%d S·%s", mdb.Len(), shortenb(mdb.Size())) c := newCMem(db.s)
stats := new(cStatsStaging)
db.logf("mem@flush N·%d S·%s", mem.mdb.Len(), shortenb(mem.mdb.Size()))
// Don't compact empty memdb. // Don't compact empty memdb.
if mdb.Len() == 0 { if mem.mdb.Len() == 0 {
db.logf("memdb@flush skipping") db.logf("mem@flush skipping")
// drop frozen memdb // drop frozen mem
db.dropFrozenMem() db.dropFrozenMem()
return return
} }
@@ -263,20 +317,13 @@ func (db *DB) memCompaction() {
return return
} }
var ( db.compactionTransactFunc("mem@flush", func(cnt *compactionTransactCounter) (err error) {
rec = &sessionRecord{}
stats = &cStatsStaging{}
flushLevel int
)
db.compactionTransactFunc("memdb@flush", func(cnt *compactionTransactCounter) (err error) {
stats.startTimer() stats.startTimer()
flushLevel, err = db.s.flushMemdb(rec, mdb.DB, -1) defer stats.stopTimer()
stats.stopTimer() return c.flush(mem.mdb, -1)
return
}, func() error { }, func() error {
for _, r := range rec.addedTables { for _, r := range c.rec.addedTables {
db.logf("memdb@flush revert @%d", r.num) db.logf("mem@flush revert @%d", r.num)
f := db.s.getTableFile(r.num) f := db.s.getTableFile(r.num)
if err := f.Remove(); err != nil { if err := f.Remove(); err != nil {
return err return err
@@ -285,23 +332,20 @@ func (db *DB) memCompaction() {
return nil return nil
}) })
db.compactionTransactFunc("memdb@commit", func(cnt *compactionTransactCounter) (err error) { db.compactionTransactFunc("mem@commit", func(cnt *compactionTransactCounter) (err error) {
stats.startTimer() stats.startTimer()
rec.setJournalNum(db.journalFile.Num()) defer stats.stopTimer()
rec.setSeqNum(db.frozenSeq) return c.commit(db.journalFile.Num(), db.frozenSeq)
err = db.s.commit(rec)
stats.stopTimer()
return
}, nil) }, nil)
db.logf("memdb@flush committed F·%d T·%v", len(rec.addedTables), stats.duration) db.logf("mem@flush committed F·%d T·%v", len(c.rec.addedTables), stats.duration)
for _, r := range rec.addedTables { for _, r := range c.rec.addedTables {
stats.write += r.size stats.write += r.size
} }
db.compStats[flushLevel].add(stats) db.compStats[c.level].add(stats)
// Drop frozen memdb. // Drop frozen mem.
db.dropFrozenMem() db.dropFrozenMem()
// Resume table compaction. // Resume table compaction.
@@ -513,7 +557,7 @@ func (b *tableCompactionBuilder) revert() error {
func (db *DB) tableCompaction(c *compaction, noTrivial bool) { func (db *DB) tableCompaction(c *compaction, noTrivial bool) {
defer c.release() defer c.release()
rec := &sessionRecord{} rec := &sessionRecord{numLevel: db.s.o.GetNumLevel()}
rec.addCompPtr(c.level, c.imax) rec.addCompPtr(c.level, c.imax)
if !noTrivial && c.trivial() { if !noTrivial && c.trivial() {
+7 -25
View File
@@ -8,7 +8,6 @@ package leveldb
import ( import (
"errors" "errors"
"math/rand"
"runtime" "runtime"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -40,11 +39,11 @@ func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.It
ti := v.getIterators(slice, ro) ti := v.getIterators(slice, ro)
n := len(ti) + 2 n := len(ti) + 2
i := make([]iterator.Iterator, 0, n) i := make([]iterator.Iterator, 0, n)
emi := em.NewIterator(slice) emi := em.mdb.NewIterator(slice)
emi.SetReleaser(&memdbReleaser{m: em}) emi.SetReleaser(&memdbReleaser{m: em})
i = append(i, emi) i = append(i, emi)
if fm != nil { if fm != nil {
fmi := fm.NewIterator(slice) fmi := fm.mdb.NewIterator(slice)
fmi.SetReleaser(&memdbReleaser{m: fm}) fmi.SetReleaser(&memdbReleaser{m: fm})
i = append(i, fmi) i = append(i, fmi)
} }
@@ -81,10 +80,6 @@ func (db *DB) newIterator(seq uint64, slice *util.Range, ro *opt.ReadOptions) *d
return iter return iter
} }
func (db *DB) iterSamplingRate() int {
return rand.Intn(2 * db.s.o.GetIteratorSamplingRate())
}
type dir int type dir int
const ( const (
@@ -103,21 +98,11 @@ type dbIter struct {
seq uint64 seq uint64
strict bool strict bool
smaplingGap int dir dir
dir dir key []byte
key []byte value []byte
value []byte err error
err error releaser util.Releaser
releaser util.Releaser
}
func (i *dbIter) sampleSeek() {
ikey := i.iter.Key()
i.smaplingGap -= len(ikey) + len(i.iter.Value())
for i.smaplingGap < 0 {
i.smaplingGap += i.db.iterSamplingRate()
i.db.sampleSeek(ikey)
}
} }
func (i *dbIter) setErr(err error) { func (i *dbIter) setErr(err error) {
@@ -190,7 +175,6 @@ func (i *dbIter) Seek(key []byte) bool {
func (i *dbIter) next() bool { func (i *dbIter) next() bool {
for { for {
if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil { if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil {
i.sampleSeek()
if seq <= i.seq { if seq <= i.seq {
switch kt { switch kt {
case ktDel: case ktDel:
@@ -241,7 +225,6 @@ func (i *dbIter) prev() bool {
if i.iter.Valid() { if i.iter.Valid() {
for { for {
if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil { if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil {
i.sampleSeek()
if seq <= i.seq { if seq <= i.seq {
if !del && i.icmp.uCompare(ukey, i.key) < 0 { if !del && i.icmp.uCompare(ukey, i.key) < 0 {
return true return true
@@ -283,7 +266,6 @@ func (i *dbIter) Prev() bool {
case dirForward: case dirForward:
for i.iter.Prev() { for i.iter.Prev() {
if ukey, _, _, kerr := parseIkey(i.iter.Key()); kerr == nil { if ukey, _, _, kerr := parseIkey(i.iter.Key()); kerr == nil {
i.sampleSeek()
if i.icmp.uCompare(ukey, i.key) < 0 { if i.icmp.uCompare(ukey, i.key) < 0 {
goto cont goto cont
} }
+7 -16
View File
@@ -15,8 +15,8 @@ import (
) )
type memDB struct { type memDB struct {
db *DB db *DB
*memdb.DB mdb *memdb.DB
ref int32 ref int32
} }
@@ -27,12 +27,12 @@ func (m *memDB) incref() {
func (m *memDB) decref() { func (m *memDB) decref() {
if ref := atomic.AddInt32(&m.ref, -1); ref == 0 { if ref := atomic.AddInt32(&m.ref, -1); ref == 0 {
// Only put back memdb with std capacity. // Only put back memdb with std capacity.
if m.Capacity() == m.db.s.o.GetWriteBuffer() { if m.mdb.Capacity() == m.db.s.o.GetWriteBuffer() {
m.Reset() m.mdb.Reset()
m.db.mpoolPut(m.DB) m.db.mpoolPut(m.mdb)
} }
m.db = nil m.db = nil
m.DB = nil m.mdb = nil
} else if ref < 0 { } else if ref < 0 {
panic("negative memdb ref") panic("negative memdb ref")
} }
@@ -48,15 +48,6 @@ func (db *DB) addSeq(delta uint64) {
atomic.AddUint64(&db.seq, delta) atomic.AddUint64(&db.seq, delta)
} }
func (db *DB) sampleSeek(ikey iKey) {
v := db.s.version()
if v.sampleSeek(ikey) {
// Trigger table compaction.
db.compSendTrigger(db.tcompCmdC)
}
v.release()
}
func (db *DB) mpoolPut(mem *memdb.DB) { func (db *DB) mpoolPut(mem *memdb.DB) {
defer func() { defer func() {
recover() recover()
@@ -126,7 +117,7 @@ func (db *DB) newMem(n int) (mem *memDB, err error) {
} }
mem = &memDB{ mem = &memDB{
db: db, db: db,
DB: mdb, mdb: mdb,
ref: 2, ref: 2,
} }
db.mem = mem db.mem = mem
+13 -135
View File
@@ -405,21 +405,19 @@ func (h *dbHarness) compactRange(min, max string) {
t.Log("DB range compaction done") t.Log("DB range compaction done")
} }
func (h *dbHarness) sizeOf(start, limit string) uint64 { func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) {
sz, err := h.db.SizeOf([]util.Range{ t := h.t
db := h.db
s, err := db.SizeOf([]util.Range{
{[]byte(start), []byte(limit)}, {[]byte(start), []byte(limit)},
}) })
if err != nil { if err != nil {
h.t.Error("SizeOf: got error: ", err) t.Error("SizeOf: got error: ", err)
} }
return sz.Sum() if s.Sum() < low || s.Sum() > hi {
} t.Errorf("sizeof %q to %q not in range, want %d - %d, got %d",
shorten(start), shorten(limit), low, hi, s.Sum())
func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) {
sz := h.sizeOf(start, limit)
if sz < low || sz > hi {
h.t.Errorf("sizeOf %q to %q not in range, want %d - %d, got %d",
shorten(start), shorten(limit), low, hi, sz)
} }
} }
@@ -2445,7 +2443,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
rec := &sessionRecord{} rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
rec.addTableFile(i, tf) rec.addTableFile(i, tf)
if err := s.commit(rec); err != nil { if err := s.commit(rec); err != nil {
t.Fatal(err) t.Fatal(err)
@@ -2455,7 +2453,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
// Build grandparent. // Build grandparent.
v := s.version() v := s.version()
c := newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...)) c := newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
rec := &sessionRecord{} rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
b := &tableCompactionBuilder{ b := &tableCompactionBuilder{
s: s, s: s,
c: c, c: c,
@@ -2479,7 +2477,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
// Build level-1. // Build level-1.
v = s.version() v = s.version()
c = newCompaction(s, v, 0, append(tFiles{}, v.tables[0]...)) c = newCompaction(s, v, 0, append(tFiles{}, v.tables[0]...))
rec = &sessionRecord{} rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
b = &tableCompactionBuilder{ b = &tableCompactionBuilder{
s: s, s: s,
c: c, c: c,
@@ -2523,7 +2521,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
// Compaction with transient error. // Compaction with transient error.
v = s.version() v = s.version()
c = newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...)) c = newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
rec = &sessionRecord{} rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
b = &tableCompactionBuilder{ b = &tableCompactionBuilder{
s: s, s: s,
c: c, c: c,
@@ -2579,123 +2577,3 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
} }
v.release() v.release()
} }
func testDB_IterTriggeredCompaction(t *testing.T, limitDiv int) {
const (
vSize = 200 * opt.KiB
tSize = 100 * opt.MiB
mIter = 100
n = tSize / vSize
)
h := newDbHarnessWopt(t, &opt.Options{
Compression: opt.NoCompression,
DisableBlockCache: true,
})
defer h.close()
key := func(x int) string {
return fmt.Sprintf("v%06d", x)
}
// Fill.
value := strings.Repeat("x", vSize)
for i := 0; i < n; i++ {
h.put(key(i), value)
}
h.compactMem()
// Delete all.
for i := 0; i < n; i++ {
h.delete(key(i))
}
h.compactMem()
var (
limit = n / limitDiv
startKey = key(0)
limitKey = key(limit)
maxKey = key(n)
slice = &util.Range{Limit: []byte(limitKey)}
initialSize0 = h.sizeOf(startKey, limitKey)
initialSize1 = h.sizeOf(limitKey, maxKey)
)
t.Logf("inital size %s [rest %s]", shortenb(int(initialSize0)), shortenb(int(initialSize1)))
for r := 0; true; r++ {
if r >= mIter {
t.Fatal("taking too long to compact")
}
// Iterates.
iter := h.db.NewIterator(slice, h.ro)
for iter.Next() {
}
if err := iter.Error(); err != nil {
t.Fatalf("Iter err: %v", err)
}
iter.Release()
// Wait compaction.
h.waitCompaction()
// Check size.
size0 := h.sizeOf(startKey, limitKey)
size1 := h.sizeOf(limitKey, maxKey)
t.Logf("#%03d size %s [rest %s]", r, shortenb(int(size0)), shortenb(int(size1)))
if size0 < initialSize0/10 {
break
}
}
if initialSize1 > 0 {
h.sizeAssert(limitKey, maxKey, initialSize1/4-opt.MiB, initialSize1+opt.MiB)
}
}
func TestDB_IterTriggeredCompaction(t *testing.T) {
testDB_IterTriggeredCompaction(t, 1)
}
func TestDB_IterTriggeredCompactionHalf(t *testing.T) {
testDB_IterTriggeredCompaction(t, 2)
}
func TestDB_ReadOnly(t *testing.T) {
h := newDbHarness(t)
defer h.close()
h.put("foo", "v1")
h.put("bar", "v2")
h.compactMem()
h.put("xfoo", "v1")
h.put("xbar", "v2")
t.Log("Trigger read-only")
if err := h.db.SetReadOnly(); err != nil {
h.close()
t.Fatalf("SetReadOnly error: %v", err)
}
h.stor.SetEmuErr(storage.TypeAll, tsOpCreate, tsOpReplace, tsOpRemove, tsOpWrite, tsOpWrite, tsOpSync)
ro := func(key, value, wantValue string) {
if err := h.db.Put([]byte(key), []byte(value), h.wo); err != ErrReadOnly {
t.Fatalf("unexpected error: %v", err)
}
h.getVal(key, wantValue)
}
ro("foo", "vx", "v1")
h.o.ReadOnly = true
h.reopenDB()
ro("foo", "vx", "v1")
ro("bar", "vx", "v2")
h.assertNumKeys(4)
}
+25 -52
View File
@@ -63,24 +63,24 @@ func (db *DB) rotateMem(n int) (mem *memDB, err error) {
return return
} }
func (db *DB) flush(n int) (mdb *memDB, mdbFree int, err error) { func (db *DB) flush(n int) (mem *memDB, nn int, err error) {
delayed := false delayed := false
flush := func() (retry bool) { flush := func() (retry bool) {
v := db.s.version() v := db.s.version()
defer v.release() defer v.release()
mdb = db.getEffectiveMem() mem = db.getEffectiveMem()
defer func() { defer func() {
if retry { if retry {
mdb.decref() mem.decref()
mdb = nil mem = nil
} }
}() }()
mdbFree = mdb.Free() nn = mem.mdb.Free()
switch { switch {
case v.tLen(0) >= db.s.o.GetWriteL0SlowdownTrigger() && !delayed: case v.tLen(0) >= db.s.o.GetWriteL0SlowdownTrigger() && !delayed:
delayed = true delayed = true
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
case mdbFree >= n: case nn >= n:
return false return false
case v.tLen(0) >= db.s.o.GetWriteL0PauseTrigger(): case v.tLen(0) >= db.s.o.GetWriteL0PauseTrigger():
delayed = true delayed = true
@@ -90,15 +90,15 @@ func (db *DB) flush(n int) (mdb *memDB, mdbFree int, err error) {
} }
default: default:
// Allow memdb to grow if it has no entry. // Allow memdb to grow if it has no entry.
if mdb.Len() == 0 { if mem.mdb.Len() == 0 {
mdbFree = n nn = n
} else { } else {
mdb.decref() mem.decref()
mdb, err = db.rotateMem(n) mem, err = db.rotateMem(n)
if err == nil { if err == nil {
mdbFree = mdb.Free() nn = mem.mdb.Free()
} else { } else {
mdbFree = 0 nn = 0
} }
} }
return false return false
@@ -129,7 +129,7 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
return return
} }
b.init(wo.GetSync() && !db.s.o.GetNoSync()) b.init(wo.GetSync())
// The write happen synchronously. // The write happen synchronously.
select { select {
@@ -157,18 +157,18 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
} }
}() }()
mdb, mdbFree, err := db.flush(b.size()) mem, memFree, err := db.flush(b.size())
if err != nil { if err != nil {
return return
} }
defer mdb.decref() defer mem.decref()
// Calculate maximum size of the batch. // Calculate maximum size of the batch.
m := 1 << 20 m := 1 << 20
if x := b.size(); x <= 128<<10 { if x := b.size(); x <= 128<<10 {
m = x + (128 << 10) m = x + (128 << 10)
} }
m = minInt(m, mdbFree) m = minInt(m, memFree)
// Merge with other batch. // Merge with other batch.
drain: drain:
@@ -197,7 +197,7 @@ drain:
select { select {
case db.journalC <- b: case db.journalC <- b:
// Write into memdb // Write into memdb
if berr := b.memReplay(mdb.DB); berr != nil { if berr := b.memReplay(mem.mdb); berr != nil {
panic(berr) panic(berr)
} }
case err = <-db.compPerErrC: case err = <-db.compPerErrC:
@@ -211,7 +211,7 @@ drain:
case err = <-db.journalAckC: case err = <-db.journalAckC:
if err != nil { if err != nil {
// Revert memdb if error detected // Revert memdb if error detected
if berr := b.revertMemReplay(mdb.DB); berr != nil { if berr := b.revertMemReplay(mem.mdb); berr != nil {
panic(berr) panic(berr)
} }
return return
@@ -225,7 +225,7 @@ drain:
if err != nil { if err != nil {
return return
} }
if berr := b.memReplay(mdb.DB); berr != nil { if berr := b.memReplay(mem.mdb); berr != nil {
panic(berr) panic(berr)
} }
} }
@@ -233,7 +233,7 @@ drain:
// Set last seq number. // Set last seq number.
db.addSeq(uint64(b.Len())) db.addSeq(uint64(b.Len()))
if b.size() >= mdbFree { if b.size() >= memFree {
db.rotateMem(0) db.rotateMem(0)
} }
return return
@@ -249,7 +249,8 @@ func (db *DB) Put(key, value []byte, wo *opt.WriteOptions) error {
return db.Write(b, wo) return db.Write(b, wo)
} }
// Delete deletes the value for the given key. // Delete deletes the value for the given key. It returns ErrNotFound if
// the DB does not contain the key.
// //
// It is safe to modify the contents of the arguments after Delete returns. // It is safe to modify the contents of the arguments after Delete returns.
func (db *DB) Delete(key []byte, wo *opt.WriteOptions) error { func (db *DB) Delete(key []byte, wo *opt.WriteOptions) error {
@@ -289,9 +290,9 @@ func (db *DB) CompactRange(r util.Range) error {
} }
// Check for overlaps in memdb. // Check for overlaps in memdb.
mdb := db.getEffectiveMem() mem := db.getEffectiveMem()
defer mdb.decref() defer mem.decref()
if isMemOverlaps(db.s.icmp, mdb.DB, r.Start, r.Limit) { if isMemOverlaps(db.s.icmp, mem.mdb, r.Start, r.Limit) {
// Memdb compaction. // Memdb compaction.
if _, err := db.rotateMem(0); err != nil { if _, err := db.rotateMem(0); err != nil {
<-db.writeLockC <-db.writeLockC
@@ -308,31 +309,3 @@ func (db *DB) CompactRange(r util.Range) error {
// Table compaction. // Table compaction.
return db.compSendRange(db.tcompCmdC, -1, r.Start, r.Limit) return db.compSendRange(db.tcompCmdC, -1, r.Start, r.Limit)
} }
// SetReadOnly makes DB read-only. It will stay read-only until reopened.
func (db *DB) SetReadOnly() error {
if err := db.ok(); err != nil {
return err
}
// Lock writer.
select {
case db.writeLockC <- struct{}{}:
db.compWriteLocking = true
case err := <-db.compPerErrC:
return err
case _, _ = <-db.closeC:
return ErrClosed
}
// Set compaction read-only.
select {
case db.compErrSetC <- ErrReadOnly:
case perr := <-db.compPerErrC:
return perr
case _, _ = <-db.closeC:
return ErrClosed
}
return nil
}
-1
View File
@@ -12,7 +12,6 @@ import (
var ( var (
ErrNotFound = errors.ErrNotFound ErrNotFound = errors.ErrNotFound
ErrReadOnly = errors.New("leveldb: read-only mode")
ErrSnapshotReleased = errors.New("leveldb: snapshot released") ErrSnapshotReleased = errors.New("leveldb: snapshot released")
ErrIterReleased = errors.New("leveldb: iterator released") ErrIterReleased = errors.New("leveldb: iterator released")
ErrClosed = errors.New("leveldb: closed") ErrClosed = errors.New("leveldb: closed")
@@ -52,14 +52,12 @@ func IsCorrupted(err error) bool {
switch err.(type) { switch err.(type) {
case *ErrCorrupted: case *ErrCorrupted:
return true return true
case *storage.ErrCorrupted:
return true
} }
return false return false
} }
// ErrMissingFiles is the type that indicating a corruption due to missing // ErrMissingFiles is the type that indicating a corruption due to missing
// files. ErrMissingFiles always wrapped with ErrCorrupted. // files.
type ErrMissingFiles struct { type ErrMissingFiles struct {
Files []*storage.FileInfo Files []*storage.FileInfo
} }
+3 -6
View File
@@ -206,7 +206,6 @@ func (p *DB) randHeight() (h int) {
return return
} }
// Must hold RW-lock if prev == true, as it use shared prevNode slice.
func (p *DB) findGE(key []byte, prev bool) (int, bool) { func (p *DB) findGE(key []byte, prev bool) (int, bool) {
node := 0 node := 0
h := p.maxHeight - 1 h := p.maxHeight - 1
@@ -303,7 +302,7 @@ func (p *DB) Put(key []byte, value []byte) error {
node := len(p.nodeData) node := len(p.nodeData)
p.nodeData = append(p.nodeData, kvOffset, len(key), len(value), h) p.nodeData = append(p.nodeData, kvOffset, len(key), len(value), h)
for i, n := range p.prevNode[:h] { for i, n := range p.prevNode[:h] {
m := n + nNext + i m := n + 4 + i
p.nodeData = append(p.nodeData, p.nodeData[m]) p.nodeData = append(p.nodeData, p.nodeData[m])
p.nodeData[m] = node p.nodeData[m] = node
} }
@@ -435,22 +434,20 @@ func (p *DB) Len() int {
// Reset resets the DB to initial empty state. Allows reuse the buffer. // Reset resets the DB to initial empty state. Allows reuse the buffer.
func (p *DB) Reset() { func (p *DB) Reset() {
p.mu.Lock()
p.rnd = rand.New(rand.NewSource(0xdeadbeef)) p.rnd = rand.New(rand.NewSource(0xdeadbeef))
p.maxHeight = 1 p.maxHeight = 1
p.n = 0 p.n = 0
p.kvSize = 0 p.kvSize = 0
p.kvData = p.kvData[:0] p.kvData = p.kvData[:0]
p.nodeData = p.nodeData[:nNext+tMaxHeight] p.nodeData = p.nodeData[:4+tMaxHeight]
p.nodeData[nKV] = 0 p.nodeData[nKV] = 0
p.nodeData[nKey] = 0 p.nodeData[nKey] = 0
p.nodeData[nVal] = 0 p.nodeData[nVal] = 0
p.nodeData[nHeight] = tMaxHeight p.nodeData[nHeight] = tMaxHeight
for n := 0; n < tMaxHeight; n++ { for n := 0; n < tMaxHeight; n++ {
p.nodeData[nNext+n] = 0 p.nodeData[4+n] = 0
p.prevNode[n] = 0 p.prevNode[n] = 0
} }
p.mu.Unlock()
} }
// New creates a new initalized in-memory key/value DB. The capacity // New creates a new initalized in-memory key/value DB. The capacity
+2 -60
View File
@@ -34,11 +34,10 @@ var (
DefaultCompactionTotalSize = 10 * MiB DefaultCompactionTotalSize = 10 * MiB
DefaultCompactionTotalSizeMultiplier = 10.0 DefaultCompactionTotalSizeMultiplier = 10.0
DefaultCompressionType = SnappyCompression DefaultCompressionType = SnappyCompression
DefaultIteratorSamplingRate = 1 * MiB
DefaultMaxMemCompationLevel = 2
DefaultNumLevel = 7
DefaultOpenFilesCacher = LRUCacher DefaultOpenFilesCacher = LRUCacher
DefaultOpenFilesCacheCapacity = 500 DefaultOpenFilesCacheCapacity = 500
DefaultMaxMemCompationLevel = 2
DefaultNumLevel = 7
DefaultWriteBuffer = 4 * MiB DefaultWriteBuffer = 4 * MiB
DefaultWriteL0PauseTrigger = 12 DefaultWriteL0PauseTrigger = 12
DefaultWriteL0SlowdownTrigger = 8 DefaultWriteL0SlowdownTrigger = 8
@@ -250,11 +249,6 @@ type Options struct {
// The default value (DefaultCompression) uses snappy compression. // The default value (DefaultCompression) uses snappy compression.
Compression Compression Compression Compression
// DisableBufferPool allows disable use of util.BufferPool functionality.
//
// The default value is false.
DisableBufferPool bool
// DisableBlockCache allows disable use of cache.Cache functionality on // DisableBlockCache allows disable use of cache.Cache functionality on
// 'sorted table' block. // 'sorted table' block.
// //
@@ -294,13 +288,6 @@ type Options struct {
// The default value is nil. // The default value is nil.
Filter filter.Filter Filter filter.Filter
// IteratorSamplingRate defines approximate gap (in bytes) between read
// sampling of an iterator. The samples will be used to determine when
// compaction should be triggered.
//
// The default is 1MiB.
IteratorSamplingRate int
// MaxMemCompationLevel defines maximum level a newly compacted 'memdb' // MaxMemCompationLevel defines maximum level a newly compacted 'memdb'
// will be pushed into if doesn't creates overlap. This should less than // will be pushed into if doesn't creates overlap. This should less than
// NumLevel. Use -1 for level-0. // NumLevel. Use -1 for level-0.
@@ -308,11 +295,6 @@ type Options struct {
// The default is 2. // The default is 2.
MaxMemCompationLevel int MaxMemCompationLevel int
// NoSync allows completely disable fsync.
//
// The default is false.
NoSync bool
// NumLevel defines number of database level. The level shouldn't changed // NumLevel defines number of database level. The level shouldn't changed
// between opens, or the database will panic. // between opens, or the database will panic.
// //
@@ -331,11 +313,6 @@ type Options struct {
// The default value is 500. // The default value is 500.
OpenFilesCacheCapacity int OpenFilesCacheCapacity int
// If true then opens DB in read-only mode.
//
// The default value is false.
ReadOnly bool
// Strict defines the DB strict level. // Strict defines the DB strict level.
Strict Strict Strict Strict
@@ -487,20 +464,6 @@ func (o *Options) GetCompression() Compression {
return o.Compression return o.Compression
} }
func (o *Options) GetDisableBufferPool() bool {
if o == nil {
return false
}
return o.DisableBufferPool
}
func (o *Options) GetDisableBlockCache() bool {
if o == nil {
return false
}
return o.DisableBlockCache
}
func (o *Options) GetDisableCompactionBackoff() bool { func (o *Options) GetDisableCompactionBackoff() bool {
if o == nil { if o == nil {
return false return false
@@ -529,13 +492,6 @@ func (o *Options) GetFilter() filter.Filter {
return o.Filter return o.Filter
} }
func (o *Options) GetIteratorSamplingRate() int {
if o == nil || o.IteratorSamplingRate <= 0 {
return DefaultIteratorSamplingRate
}
return o.IteratorSamplingRate
}
func (o *Options) GetMaxMemCompationLevel() int { func (o *Options) GetMaxMemCompationLevel() int {
level := DefaultMaxMemCompationLevel level := DefaultMaxMemCompationLevel
if o != nil { if o != nil {
@@ -551,13 +507,6 @@ func (o *Options) GetMaxMemCompationLevel() int {
return level return level
} }
func (o *Options) GetNoSync() bool {
if o == nil {
return false
}
return o.NoSync
}
func (o *Options) GetNumLevel() int { func (o *Options) GetNumLevel() int {
if o == nil || o.NumLevel <= 0 { if o == nil || o.NumLevel <= 0 {
return DefaultNumLevel return DefaultNumLevel
@@ -584,13 +533,6 @@ func (o *Options) GetOpenFilesCacheCapacity() int {
return o.OpenFilesCacheCapacity return o.OpenFilesCacheCapacity
} }
func (o *Options) GetReadOnly() bool {
if o == nil {
return false
}
return o.ReadOnly
}
func (o *Options) GetStrict(strict Strict) bool { func (o *Options) GetStrict(strict Strict) bool {
if o == nil || o.Strict == 0 { if o == nil || o.Strict == 0 {
return DefaultStrict&strict != 0 return DefaultStrict&strict != 0
+254 -10
View File
@@ -11,8 +11,10 @@ import (
"io" "io"
"os" "os"
"sync" "sync"
"sync/atomic"
"github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/journal" "github.com/syndtr/goleveldb/leveldb/journal"
"github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/storage" "github.com/syndtr/goleveldb/leveldb/storage"
@@ -125,16 +127,11 @@ func (s *session) recover() (err error) {
return return
} }
defer reader.Close() defer reader.Close()
strict := s.o.GetStrict(opt.StrictManifest)
jr := journal.NewReader(reader, dropper{s, m}, strict, true)
var ( staging := s.stVersion.newStaging()
// Options. rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
numLevel = s.o.GetNumLevel()
strict = s.o.GetStrict(opt.StrictManifest)
jr = journal.NewReader(reader, dropper{s, m}, strict, true)
rec = &sessionRecord{}
staging = s.stVersion.newStaging()
)
for { for {
var r io.Reader var r io.Reader
r, err = jr.Next() r, err = jr.Next()
@@ -146,7 +143,7 @@ func (s *session) recover() (err error) {
return errors.SetFile(err, m) return errors.SetFile(err, m)
} }
err = rec.decode(r, numLevel) err = rec.decode(r)
if err == nil { if err == nil {
// save compact pointers // save compact pointers
for _, r := range rec.compPtrs { for _, r := range rec.compPtrs {
@@ -209,3 +206,250 @@ func (s *session) commit(r *sessionRecord) (err error) {
return return
} }
// Pick a compaction based on current state; need external synchronization.
func (s *session) pickCompaction() *compaction {
v := s.version()
var level int
var t0 tFiles
if v.cScore >= 1 {
level = v.cLevel
cptr := s.stCompPtrs[level]
tables := v.tables[level]
for _, t := range tables {
if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 {
t0 = append(t0, t)
break
}
}
if len(t0) == 0 {
t0 = append(t0, tables[0])
}
} else {
if p := atomic.LoadPointer(&v.cSeek); p != nil {
ts := (*tSet)(p)
level = ts.level
t0 = append(t0, ts.table)
} else {
v.release()
return nil
}
}
return newCompaction(s, v, level, t0)
}
// Create compaction from given level and range; need external synchronization.
func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction {
v := s.version()
t0 := v.tables[level].getOverlaps(nil, s.icmp, umin, umax, level == 0)
if len(t0) == 0 {
v.release()
return nil
}
// Avoid compacting too much in one shot in case the range is large.
// But we cannot do this for level-0 since level-0 files can overlap
// and we must not pick one file and drop another older file if the
// two files overlap.
if level > 0 {
limit := uint64(v.s.o.GetCompactionSourceLimit(level))
total := uint64(0)
for i, t := range t0 {
total += t.size
if total >= limit {
s.logf("table@compaction limiting F·%d -> F·%d", len(t0), i+1)
t0 = t0[:i+1]
break
}
}
}
return newCompaction(s, v, level, t0)
}
func newCompaction(s *session, v *version, level int, t0 tFiles) *compaction {
c := &compaction{
s: s,
v: v,
level: level,
tables: [2]tFiles{t0, nil},
maxGPOverlaps: uint64(s.o.GetCompactionGPOverlaps(level)),
tPtrs: make([]int, s.o.GetNumLevel()),
}
c.expand()
c.save()
return c
}
// compaction represent a compaction state.
type compaction struct {
s *session
v *version
level int
tables [2]tFiles
maxGPOverlaps uint64
gp tFiles
gpi int
seenKey bool
gpOverlappedBytes uint64
imin, imax iKey
tPtrs []int
released bool
snapGPI int
snapSeenKey bool
snapGPOverlappedBytes uint64
snapTPtrs []int
}
func (c *compaction) save() {
c.snapGPI = c.gpi
c.snapSeenKey = c.seenKey
c.snapGPOverlappedBytes = c.gpOverlappedBytes
c.snapTPtrs = append(c.snapTPtrs[:0], c.tPtrs...)
}
func (c *compaction) restore() {
c.gpi = c.snapGPI
c.seenKey = c.snapSeenKey
c.gpOverlappedBytes = c.snapGPOverlappedBytes
c.tPtrs = append(c.tPtrs[:0], c.snapTPtrs...)
}
func (c *compaction) release() {
if !c.released {
c.released = true
c.v.release()
}
}
// Expand compacted tables; need external synchronization.
func (c *compaction) expand() {
limit := uint64(c.s.o.GetCompactionExpandLimit(c.level))
vt0, vt1 := c.v.tables[c.level], c.v.tables[c.level+1]
t0, t1 := c.tables[0], c.tables[1]
imin, imax := t0.getRange(c.s.icmp)
// We expand t0 here just incase ukey hop across tables.
t0 = vt0.getOverlaps(t0, c.s.icmp, imin.ukey(), imax.ukey(), c.level == 0)
if len(t0) != len(c.tables[0]) {
imin, imax = t0.getRange(c.s.icmp)
}
t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false)
// Get entire range covered by compaction.
amin, amax := append(t0, t1...).getRange(c.s.icmp)
// See if we can grow the number of inputs in "level" without
// changing the number of "level+1" files we pick up.
if len(t1) > 0 {
exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), c.level == 0)
if len(exp0) > len(t0) && t1.size()+exp0.size() < limit {
xmin, xmax := exp0.getRange(c.s.icmp)
exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false)
if len(exp1) == len(t1) {
c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)",
c.level, c.level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())),
len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size())))
imin, imax = xmin, xmax
t0, t1 = exp0, exp1
amin, amax = append(t0, t1...).getRange(c.s.icmp)
}
}
}
// Compute the set of grandparent files that overlap this compaction
// (parent == level+1; grandparent == level+2)
if c.level+2 < c.s.o.GetNumLevel() {
c.gp = c.v.tables[c.level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false)
}
c.tables[0], c.tables[1] = t0, t1
c.imin, c.imax = imin, imax
}
// Check whether compaction is trivial.
func (c *compaction) trivial() bool {
return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= c.maxGPOverlaps
}
func (c *compaction) baseLevelForKey(ukey []byte) bool {
for level, tables := range c.v.tables[c.level+2:] {
for c.tPtrs[level] < len(tables) {
t := tables[c.tPtrs[level]]
if c.s.icmp.uCompare(ukey, t.imax.ukey()) <= 0 {
// We've advanced far enough.
if c.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 {
// Key falls in this file's range, so definitely not base level.
return false
}
break
}
c.tPtrs[level]++
}
}
return true
}
func (c *compaction) shouldStopBefore(ikey iKey) bool {
for ; c.gpi < len(c.gp); c.gpi++ {
gp := c.gp[c.gpi]
if c.s.icmp.Compare(ikey, gp.imax) <= 0 {
break
}
if c.seenKey {
c.gpOverlappedBytes += gp.size
}
}
c.seenKey = true
if c.gpOverlappedBytes > c.maxGPOverlaps {
// Too much overlap for current output; start new output.
c.gpOverlappedBytes = 0
return true
}
return false
}
// Creates an iterator.
func (c *compaction) newIterator() iterator.Iterator {
// Creates iterator slice.
icap := len(c.tables)
if c.level == 0 {
// Special case for level-0
icap = len(c.tables[0]) + 1
}
its := make([]iterator.Iterator, 0, icap)
// Options.
ro := &opt.ReadOptions{
DontFillCache: true,
Strict: opt.StrictOverride,
}
strict := c.s.o.GetStrict(opt.StrictCompaction)
if strict {
ro.Strict |= opt.StrictReader
}
for i, tables := range c.tables {
if len(tables) == 0 {
continue
}
// Level-0 is not sorted and may overlaps each other.
if c.level+i == 0 {
for _, t := range tables {
its = append(its, c.s.tops.newIterator(t, nil, ro))
}
} else {
it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict)
its = append(its, it)
}
}
return iterator.NewMergedIterator(its, c.s.icmp, strict)
}
@@ -1,287 +0,0 @@
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package leveldb
import (
"sync/atomic"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/memdb"
"github.com/syndtr/goleveldb/leveldb/opt"
)
func (s *session) pickMemdbLevel(umin, umax []byte) int {
v := s.version()
defer v.release()
return v.pickMemdbLevel(umin, umax)
}
func (s *session) flushMemdb(rec *sessionRecord, mdb *memdb.DB, level int) (level_ int, err error) {
// Create sorted table.
iter := mdb.NewIterator(nil)
defer iter.Release()
t, n, err := s.tops.createFrom(iter)
if err != nil {
return level, err
}
// Pick level and add to record.
if level < 0 {
level = s.pickMemdbLevel(t.imin.ukey(), t.imax.ukey())
}
rec.addTableFile(level, t)
s.logf("memdb@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.imin, t.imax)
return level, nil
}
// Pick a compaction based on current state; need external synchronization.
func (s *session) pickCompaction() *compaction {
v := s.version()
var level int
var t0 tFiles
if v.cScore >= 1 {
level = v.cLevel
cptr := s.stCompPtrs[level]
tables := v.tables[level]
for _, t := range tables {
if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 {
t0 = append(t0, t)
break
}
}
if len(t0) == 0 {
t0 = append(t0, tables[0])
}
} else {
if p := atomic.LoadPointer(&v.cSeek); p != nil {
ts := (*tSet)(p)
level = ts.level
t0 = append(t0, ts.table)
} else {
v.release()
return nil
}
}
return newCompaction(s, v, level, t0)
}
// Create compaction from given level and range; need external synchronization.
func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction {
v := s.version()
t0 := v.tables[level].getOverlaps(nil, s.icmp, umin, umax, level == 0)
if len(t0) == 0 {
v.release()
return nil
}
// Avoid compacting too much in one shot in case the range is large.
// But we cannot do this for level-0 since level-0 files can overlap
// and we must not pick one file and drop another older file if the
// two files overlap.
if level > 0 {
limit := uint64(v.s.o.GetCompactionSourceLimit(level))
total := uint64(0)
for i, t := range t0 {
total += t.size
if total >= limit {
s.logf("table@compaction limiting F·%d -> F·%d", len(t0), i+1)
t0 = t0[:i+1]
break
}
}
}
return newCompaction(s, v, level, t0)
}
func newCompaction(s *session, v *version, level int, t0 tFiles) *compaction {
c := &compaction{
s: s,
v: v,
level: level,
tables: [2]tFiles{t0, nil},
maxGPOverlaps: uint64(s.o.GetCompactionGPOverlaps(level)),
tPtrs: make([]int, s.o.GetNumLevel()),
}
c.expand()
c.save()
return c
}
// compaction represent a compaction state.
type compaction struct {
s *session
v *version
level int
tables [2]tFiles
maxGPOverlaps uint64
gp tFiles
gpi int
seenKey bool
gpOverlappedBytes uint64
imin, imax iKey
tPtrs []int
released bool
snapGPI int
snapSeenKey bool
snapGPOverlappedBytes uint64
snapTPtrs []int
}
func (c *compaction) save() {
c.snapGPI = c.gpi
c.snapSeenKey = c.seenKey
c.snapGPOverlappedBytes = c.gpOverlappedBytes
c.snapTPtrs = append(c.snapTPtrs[:0], c.tPtrs...)
}
func (c *compaction) restore() {
c.gpi = c.snapGPI
c.seenKey = c.snapSeenKey
c.gpOverlappedBytes = c.snapGPOverlappedBytes
c.tPtrs = append(c.tPtrs[:0], c.snapTPtrs...)
}
func (c *compaction) release() {
if !c.released {
c.released = true
c.v.release()
}
}
// Expand compacted tables; need external synchronization.
func (c *compaction) expand() {
limit := uint64(c.s.o.GetCompactionExpandLimit(c.level))
vt0, vt1 := c.v.tables[c.level], c.v.tables[c.level+1]
t0, t1 := c.tables[0], c.tables[1]
imin, imax := t0.getRange(c.s.icmp)
// We expand t0 here just incase ukey hop across tables.
t0 = vt0.getOverlaps(t0, c.s.icmp, imin.ukey(), imax.ukey(), c.level == 0)
if len(t0) != len(c.tables[0]) {
imin, imax = t0.getRange(c.s.icmp)
}
t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false)
// Get entire range covered by compaction.
amin, amax := append(t0, t1...).getRange(c.s.icmp)
// See if we can grow the number of inputs in "level" without
// changing the number of "level+1" files we pick up.
if len(t1) > 0 {
exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), c.level == 0)
if len(exp0) > len(t0) && t1.size()+exp0.size() < limit {
xmin, xmax := exp0.getRange(c.s.icmp)
exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false)
if len(exp1) == len(t1) {
c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)",
c.level, c.level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())),
len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size())))
imin, imax = xmin, xmax
t0, t1 = exp0, exp1
amin, amax = append(t0, t1...).getRange(c.s.icmp)
}
}
}
// Compute the set of grandparent files that overlap this compaction
// (parent == level+1; grandparent == level+2)
if c.level+2 < c.s.o.GetNumLevel() {
c.gp = c.v.tables[c.level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false)
}
c.tables[0], c.tables[1] = t0, t1
c.imin, c.imax = imin, imax
}
// Check whether compaction is trivial.
func (c *compaction) trivial() bool {
return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= c.maxGPOverlaps
}
func (c *compaction) baseLevelForKey(ukey []byte) bool {
for level, tables := range c.v.tables[c.level+2:] {
for c.tPtrs[level] < len(tables) {
t := tables[c.tPtrs[level]]
if c.s.icmp.uCompare(ukey, t.imax.ukey()) <= 0 {
// We've advanced far enough.
if c.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 {
// Key falls in this file's range, so definitely not base level.
return false
}
break
}
c.tPtrs[level]++
}
}
return true
}
func (c *compaction) shouldStopBefore(ikey iKey) bool {
for ; c.gpi < len(c.gp); c.gpi++ {
gp := c.gp[c.gpi]
if c.s.icmp.Compare(ikey, gp.imax) <= 0 {
break
}
if c.seenKey {
c.gpOverlappedBytes += gp.size
}
}
c.seenKey = true
if c.gpOverlappedBytes > c.maxGPOverlaps {
// Too much overlap for current output; start new output.
c.gpOverlappedBytes = 0
return true
}
return false
}
// Creates an iterator.
func (c *compaction) newIterator() iterator.Iterator {
// Creates iterator slice.
icap := len(c.tables)
if c.level == 0 {
// Special case for level-0.
icap = len(c.tables[0]) + 1
}
its := make([]iterator.Iterator, 0, icap)
// Options.
ro := &opt.ReadOptions{
DontFillCache: true,
Strict: opt.StrictOverride,
}
strict := c.s.o.GetStrict(opt.StrictCompaction)
if strict {
ro.Strict |= opt.StrictReader
}
for i, tables := range c.tables {
if len(tables) == 0 {
continue
}
// Level-0 is not sorted and may overlaps each other.
if c.level+i == 0 {
for _, t := range tables {
its = append(its, c.s.tops.newIterator(t, nil, ro))
}
} else {
it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict)
its = append(its, it)
}
}
return iterator.NewMergedIterator(its, c.s.icmp, strict)
}
@@ -52,6 +52,8 @@ type dtRecord struct {
} }
type sessionRecord struct { type sessionRecord struct {
numLevel int
hasRec int hasRec int
comparer string comparer string
journalNum uint64 journalNum uint64
@@ -228,7 +230,7 @@ func (p *sessionRecord) readBytes(field string, r byteReader) []byte {
return x return x
} }
func (p *sessionRecord) readLevel(field string, r io.ByteReader, numLevel int) int { func (p *sessionRecord) readLevel(field string, r io.ByteReader) int {
if p.err != nil { if p.err != nil {
return 0 return 0
} }
@@ -236,14 +238,14 @@ func (p *sessionRecord) readLevel(field string, r io.ByteReader, numLevel int) i
if p.err != nil { if p.err != nil {
return 0 return 0
} }
if x >= uint64(numLevel) { if x >= uint64(p.numLevel) {
p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "invalid level number"}) p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "invalid level number"})
return 0 return 0
} }
return int(x) return int(x)
} }
func (p *sessionRecord) decode(r io.Reader, numLevel int) error { func (p *sessionRecord) decode(r io.Reader) error {
br, ok := r.(byteReader) br, ok := r.(byteReader)
if !ok { if !ok {
br = bufio.NewReader(r) br = bufio.NewReader(r)
@@ -284,13 +286,13 @@ func (p *sessionRecord) decode(r io.Reader, numLevel int) error {
p.setSeqNum(x) p.setSeqNum(x)
} }
case recCompPtr: case recCompPtr:
level := p.readLevel("comp-ptr.level", br, numLevel) level := p.readLevel("comp-ptr.level", br)
ikey := p.readBytes("comp-ptr.ikey", br) ikey := p.readBytes("comp-ptr.ikey", br)
if p.err == nil { if p.err == nil {
p.addCompPtr(level, iKey(ikey)) p.addCompPtr(level, iKey(ikey))
} }
case recAddTable: case recAddTable:
level := p.readLevel("add-table.level", br, numLevel) level := p.readLevel("add-table.level", br)
num := p.readUvarint("add-table.num", br) num := p.readUvarint("add-table.num", br)
size := p.readUvarint("add-table.size", br) size := p.readUvarint("add-table.size", br)
imin := p.readBytes("add-table.imin", br) imin := p.readBytes("add-table.imin", br)
@@ -299,7 +301,7 @@ func (p *sessionRecord) decode(r io.Reader, numLevel int) error {
p.addTable(level, num, size, imin, imax) p.addTable(level, num, size, imin, imax)
} }
case recDelTable: case recDelTable:
level := p.readLevel("del-table.level", br, numLevel) level := p.readLevel("del-table.level", br)
num := p.readUvarint("del-table.num", br) num := p.readUvarint("del-table.num", br)
if p.err == nil { if p.err == nil {
p.delTable(level, num) p.delTable(level, num)
@@ -19,8 +19,8 @@ func decodeEncode(v *sessionRecord) (res bool, err error) {
if err != nil { if err != nil {
return return
} }
v2 := &sessionRecord{} v2 := &sessionRecord{numLevel: opt.DefaultNumLevel}
err = v.decode(b, opt.DefaultNumLevel) err = v.decode(b)
if err != nil { if err != nil {
return return
} }
@@ -34,7 +34,7 @@ func decodeEncode(v *sessionRecord) (res bool, err error) {
func TestSessionRecord_EncodeDecode(t *testing.T) { func TestSessionRecord_EncodeDecode(t *testing.T) {
big := uint64(1) << 50 big := uint64(1) << 50
v := &sessionRecord{} v := &sessionRecord{numLevel: opt.DefaultNumLevel}
i := uint64(0) i := uint64(0)
test := func() { test := func() {
res, err := decodeEncode(v) res, err := decodeEncode(v)
+4 -6
View File
@@ -182,7 +182,7 @@ func (s *session) newManifest(rec *sessionRecord, v *version) (err error) {
defer v.release() defer v.release()
} }
if rec == nil { if rec == nil {
rec = &sessionRecord{} rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
} }
s.fillRecord(rec, true) s.fillRecord(rec, true)
v.fillRecord(rec) v.fillRecord(rec)
@@ -240,11 +240,9 @@ func (s *session) flushManifest(rec *sessionRecord) (err error) {
if err != nil { if err != nil {
return return
} }
if !s.o.GetNoSync() { err = s.manifestWriter.Sync()
err = s.manifestWriter.Sync() if err != nil {
if err != nil { return
return
}
} }
s.recordCommited(rec) s.recordCommited(rec)
return return
@@ -243,10 +243,7 @@ func (fs *fileStorage) GetManifest() (f File, err error) {
rem = append(rem, fn) rem = append(rem, fn)
} }
if !pend1 || cerr == nil { if !pend1 || cerr == nil {
cerr = &ErrCorrupted{ cerr = fmt.Errorf("leveldb/storage: corrupted or incomplete %s file", fn)
File: fsParseName(filepath.Base(fn)),
Err: errors.New("leveldb/storage: corrupted or incomplete manifest file"),
}
} }
} else if f != nil && f1.Num() < f.Num() { } else if f != nil && f1.Num() < f.Num() {
fs.log(fmt.Sprintf("skipping %s: obsolete", fn)) fs.log(fmt.Sprintf("skipping %s: obsolete", fn))
@@ -329,7 +326,8 @@ func (fs *fileStorage) Close() error {
runtime.SetFinalizer(fs, nil) runtime.SetFinalizer(fs, nil)
if fs.open > 0 { if fs.open > 0 {
fs.log(fmt.Sprintf("close: warning, %d files still open", fs.open)) fs.log(fmt.Sprintf("refuse to close, %d files still open", fs.open))
return fmt.Errorf("leveldb/storage: cannot close, %d files still open", fs.open)
} }
fs.open = -1 fs.open = -1
e1 := fs.logw.Close() e1 := fs.logw.Close()
@@ -507,37 +505,30 @@ func (f *file) path() string {
return filepath.Join(f.fs.path, f.name()) return filepath.Join(f.fs.path, f.name())
} }
func fsParseName(name string) *FileInfo { func (f *file) parse(name string) bool {
fi := &FileInfo{} var num uint64
var tail string var tail string
_, err := fmt.Sscanf(name, "%d.%s", &fi.Num, &tail) _, err := fmt.Sscanf(name, "%d.%s", &num, &tail)
if err == nil { if err == nil {
switch tail { switch tail {
case "log": case "log":
fi.Type = TypeJournal f.t = TypeJournal
case "ldb", "sst": case "ldb", "sst":
fi.Type = TypeTable f.t = TypeTable
case "tmp": case "tmp":
fi.Type = TypeTemp f.t = TypeTemp
default: default:
return nil return false
} }
return fi f.num = num
return true
} }
n, _ := fmt.Sscanf(name, "MANIFEST-%d%s", &fi.Num, &tail) n, _ := fmt.Sscanf(name, "MANIFEST-%d%s", &num, &tail)
if n == 1 { if n == 1 {
fi.Type = TypeManifest f.t = TypeManifest
return fi f.num = num
return true
} }
return nil
}
func (f *file) parse(name string) bool { return false
fi := fsParseName(name)
if fi == nil {
return false
}
f.t = fi.Type
f.num = fi.Num
return true
} }
@@ -50,23 +50,13 @@ func rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath) return os.Rename(oldpath, newpath)
} }
func isErrInvalid(err error) bool {
if err == os.ErrInvalid {
return true
}
if syserr, ok := err.(*os.SyscallError); ok && syserr.Err == syscall.EINVAL {
return true
}
return false
}
func syncDir(name string) error { func syncDir(name string) error {
f, err := os.Open(name) f, err := os.Open(name)
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer f.Close()
if err := f.Sync(); err != nil && !isErrInvalid(err) { if err := f.Sync(); err != nil {
return err return err
} }
return nil return nil
@@ -46,22 +46,6 @@ var (
ErrClosed = errors.New("leveldb/storage: closed") ErrClosed = errors.New("leveldb/storage: closed")
) )
// ErrCorrupted is the type that wraps errors that indicate corruption of
// a file. Package storage has its own type instead of using
// errors.ErrCorrupted to prevent circular import.
type ErrCorrupted struct {
File *FileInfo
Err error
}
func (e *ErrCorrupted) Error() string {
if e.File != nil {
return fmt.Sprintf("%v [file=%v]", e.Err, e.File)
} else {
return e.Err.Error()
}
}
// Syncer is the interface that wraps basic Sync method. // Syncer is the interface that wraps basic Sync method.
type Syncer interface { type Syncer interface {
// Sync commits the current contents of the file to stable storage. // Sync commits the current contents of the file to stable storage.
@@ -42,8 +42,6 @@ type tsOp uint
const ( const (
tsOpOpen tsOp = iota tsOpOpen tsOp = iota
tsOpCreate tsOpCreate
tsOpReplace
tsOpRemove
tsOpRead tsOpRead
tsOpReadAt tsOpReadAt
tsOpWrite tsOpWrite
@@ -243,10 +241,6 @@ func (tf tsFile) Replace(newfile storage.File) (err error) {
if err != nil { if err != nil {
return return
} }
if tf.shouldErr(tsOpReplace) {
err = errors.New("leveldb.testStorage: emulated create error")
return
}
err = tf.File.Replace(newfile.(tsFile).File) err = tf.File.Replace(newfile.(tsFile).File)
if err != nil { if err != nil {
ts.t.Errorf("E: cannot replace file, num=%d type=%v: %v", tf.Num(), tf.Type(), err) ts.t.Errorf("E: cannot replace file, num=%d type=%v: %v", tf.Num(), tf.Type(), err)
@@ -264,10 +258,6 @@ func (tf tsFile) Remove() (err error) {
if err != nil { if err != nil {
return return
} }
if tf.shouldErr(tsOpRemove) {
err = errors.New("leveldb.testStorage: emulated create error")
return
}
err = tf.File.Remove() err = tf.File.Remove()
if err != nil { if err != nil {
ts.t.Errorf("E: cannot remove file, num=%d type=%v: %v", tf.Num(), tf.Type(), err) ts.t.Errorf("E: cannot remove file, num=%d type=%v: %v", tf.Num(), tf.Type(), err)
+5 -13
View File
@@ -287,7 +287,6 @@ func (x *tFilesSortByNum) Less(i, j int) bool {
// Table operations. // Table operations.
type tOps struct { type tOps struct {
s *session s *session
noSync bool
cache *cache.Cache cache *cache.Cache
bcache *cache.Cache bcache *cache.Cache
bpool *util.BufferPool bpool *util.BufferPool
@@ -442,27 +441,22 @@ func newTableOps(s *session) *tOps {
var ( var (
cacher cache.Cacher cacher cache.Cacher
bcache *cache.Cache bcache *cache.Cache
bpool *util.BufferPool
) )
if s.o.GetOpenFilesCacheCapacity() > 0 { if s.o.GetOpenFilesCacheCapacity() > 0 {
cacher = cache.NewLRU(s.o.GetOpenFilesCacheCapacity()) cacher = cache.NewLRU(s.o.GetOpenFilesCacheCapacity())
} }
if !s.o.GetDisableBlockCache() { if !s.o.DisableBlockCache {
var bcacher cache.Cacher var bcacher cache.Cacher
if s.o.GetBlockCacheCapacity() > 0 { if s.o.GetBlockCacheCapacity() > 0 {
bcacher = cache.NewLRU(s.o.GetBlockCacheCapacity()) bcacher = cache.NewLRU(s.o.GetBlockCacheCapacity())
} }
bcache = cache.NewCache(bcacher) bcache = cache.NewCache(bcacher)
} }
if !s.o.GetDisableBufferPool() {
bpool = util.NewBufferPool(s.o.GetBlockSize() + 5)
}
return &tOps{ return &tOps{
s: s, s: s,
noSync: s.o.GetNoSync(),
cache: cache.NewCache(cacher), cache: cache.NewCache(cacher),
bcache: bcache, bcache: bcache,
bpool: bpool, bpool: util.NewBufferPool(s.o.GetBlockSize() + 5),
} }
} }
@@ -507,11 +501,9 @@ func (w *tWriter) finish() (f *tFile, err error) {
if err != nil { if err != nil {
return return
} }
if !w.t.noSync { err = w.w.Sync()
err = w.w.Sync() if err != nil {
if err != nil { return
return
}
} }
f = newTableFile(w.file, uint64(w.tw.BytesLen()), iKey(w.first), iKey(w.last)) f = newTableFile(w.file, uint64(w.tw.BytesLen()), iKey(w.first), iKey(w.last))
return return
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/golang/snappy" "github.com/syndtr/gosnappy/snappy"
"github.com/syndtr/goleveldb/leveldb/cache" "github.com/syndtr/goleveldb/leveldb/cache"
"github.com/syndtr/goleveldb/leveldb/comparer" "github.com/syndtr/goleveldb/leveldb/comparer"
+6 -2
View File
@@ -12,7 +12,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/golang/snappy" "github.com/syndtr/gosnappy/snappy"
"github.com/syndtr/goleveldb/leveldb/comparer" "github.com/syndtr/goleveldb/leveldb/comparer"
"github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/filter"
@@ -167,7 +167,11 @@ func (w *Writer) writeBlock(buf *util.Buffer, compression opt.Compression) (bh b
if n := snappy.MaxEncodedLen(buf.Len()) + blockTrailerLen; len(w.compressionScratch) < n { if n := snappy.MaxEncodedLen(buf.Len()) + blockTrailerLen; len(w.compressionScratch) < n {
w.compressionScratch = make([]byte, n) w.compressionScratch = make([]byte, n)
} }
compressed := snappy.Encode(w.compressionScratch, buf.Bytes()) var compressed []byte
compressed, err = snappy.Encode(w.compressionScratch, buf.Bytes())
if err != nil {
return
}
n := len(compressed) n := len(compressed)
b = compressed[:n+blockTrailerLen] b = compressed[:n+blockTrailerLen]
b[n] = blockTypeSnappyCompression b[n] = blockTypeSnappyCompression
@@ -201,7 +201,6 @@ func (p *BufferPool) String() string {
func (p *BufferPool) drain() { func (p *BufferPool) drain() {
ticker := time.NewTicker(2 * time.Second) ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
+3 -24
View File
@@ -136,8 +136,9 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byt
if !tseek { if !tseek {
if tset == nil { if tset == nil {
tset = &tSet{level, t} tset = &tSet{level, t}
} else { } else if tset.table.consumeSeek() <= 0 {
tseek = true tseek = true
tcomp = atomic.CompareAndSwapPointer(&v.cSeek, nil, unsafe.Pointer(tset))
} }
} }
@@ -202,28 +203,6 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byt
return true return true
}) })
if tseek && tset.table.consumeSeek() <= 0 {
tcomp = atomic.CompareAndSwapPointer(&v.cSeek, nil, unsafe.Pointer(tset))
}
return
}
func (v *version) sampleSeek(ikey iKey) (tcomp bool) {
var tset *tSet
v.walkOverlapping(ikey, func(level int, t *tFile) bool {
if tset == nil {
tset = &tSet{level, t}
return true
} else {
if tset.table.consumeSeek() <= 0 {
tcomp = atomic.CompareAndSwapPointer(&v.cSeek, nil, unsafe.Pointer(tset))
}
return false
}
}, nil)
return return
} }
@@ -300,7 +279,7 @@ func (v *version) offsetOf(ikey iKey) (n uint64, err error) {
return return
} }
func (v *version) pickMemdbLevel(umin, umax []byte) (level int) { func (v *version) pickLevel(umin, umax []byte) (level int) {
if !v.tables[0].overlaps(v.s.icmp, umin, umax, true) { if !v.tables[0].overlaps(v.s.icmp, umin, umax, true) {
var overlaps tFiles var overlaps tFiles
maxLevel := v.s.o.GetMaxMemCompationLevel() maxLevel := v.s.o.GetMaxMemCompationLevel()
@@ -13,8 +13,6 @@ import (
var ( var (
// ErrCorrupt reports that the input is invalid. // ErrCorrupt reports that the input is invalid.
ErrCorrupt = errors.New("snappy: corrupt input") ErrCorrupt = errors.New("snappy: corrupt input")
// ErrTooLarge reports that the uncompressed length is too large.
ErrTooLarge = errors.New("snappy: decoded block is too large")
// ErrUnsupported reports that the input isn't supported. // ErrUnsupported reports that the input isn't supported.
ErrUnsupported = errors.New("snappy: unsupported input") ErrUnsupported = errors.New("snappy: unsupported input")
) )
@@ -29,13 +27,11 @@ func DecodedLen(src []byte) (int, error) {
// that the length header occupied. // that the length header occupied.
func decodedLen(src []byte) (blockLen, headerLen int, err error) { func decodedLen(src []byte) (blockLen, headerLen int, err error) {
v, n := binary.Uvarint(src) v, n := binary.Uvarint(src)
if n <= 0 || v > 0xffffffff { if n == 0 {
return 0, 0, ErrCorrupt return 0, 0, ErrCorrupt
} }
if uint64(int(v)) != v {
const wordSize = 32 << (^uint(0) >> 32 & 1) return 0, 0, errors.New("snappy: decoded block is too large")
if wordSize == 32 && v > 0x7fffffff {
return 0, 0, ErrTooLarge
} }
return int(v), n, nil return int(v), n, nil
} }
@@ -60,7 +56,7 @@ func Decode(dst, src []byte) ([]byte, error) {
x := uint(src[s] >> 2) x := uint(src[s] >> 2)
switch { switch {
case x < 60: case x < 60:
s++ s += 1
case x == 60: case x == 60:
s += 2 s += 2
if s > len(src) { if s > len(src) {
@@ -134,7 +130,7 @@ func Decode(dst, src []byte) ([]byte, error) {
// NewReader returns a new Reader that decompresses from r, using the framing // NewReader returns a new Reader that decompresses from r, using the framing
// format described at // format described at
// https://github.com/google/snappy/blob/master/framing_format.txt // https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
func NewReader(r io.Reader) *Reader { func NewReader(r io.Reader) *Reader {
return &Reader{ return &Reader{
r: r, r: r,
@@ -204,7 +200,7 @@ func (r *Reader) Read(p []byte) (int, error) {
} }
// The chunk types are specified at // The chunk types are specified at
// https://github.com/google/snappy/blob/master/framing_format.txt // https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
switch chunkType { switch chunkType {
case chunkTypeCompressedData: case chunkTypeCompressedData:
// Section 4.2. Compressed data (chunk type 0x00). // Section 4.2. Compressed data (chunk type 0x00).
@@ -284,11 +280,13 @@ func (r *Reader) Read(p []byte) (int, error) {
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
r.err = ErrUnsupported r.err = ErrUnsupported
return 0, r.err return 0, r.err
}
// Section 4.4 Padding (chunk type 0xfe). } else {
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). // Section 4.4 Padding (chunk type 0xfe).
if !r.readFull(r.buf[:chunkLen]) { // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
return 0, r.err if !r.readFull(r.buf[:chunkLen]) {
return 0, r.err
}
} }
} }
} }
@@ -79,7 +79,7 @@ func emitCopy(dst []byte, offset, length int) int {
// slice of dst if dst was large enough to hold the entire encoded block. // slice of dst if dst was large enough to hold the entire encoded block.
// Otherwise, a newly allocated slice will be returned. // Otherwise, a newly allocated slice will be returned.
// It is valid to pass a nil dst. // It is valid to pass a nil dst.
func Encode(dst, src []byte) []byte { func Encode(dst, src []byte) ([]byte, error) {
if n := MaxEncodedLen(len(src)); len(dst) < n { if n := MaxEncodedLen(len(src)); len(dst) < n {
dst = make([]byte, n) dst = make([]byte, n)
} }
@@ -92,7 +92,7 @@ func Encode(dst, src []byte) []byte {
if len(src) != 0 { if len(src) != 0 {
d += emitLiteral(dst[d:], src) d += emitLiteral(dst[d:], src)
} }
return dst[:d] return dst[:d], nil
} }
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive. // Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
@@ -145,7 +145,7 @@ func Encode(dst, src []byte) []byte {
if lit != len(src) { if lit != len(src) {
d += emitLiteral(dst[d:], src[lit:]) d += emitLiteral(dst[d:], src[lit:])
} }
return dst[:d] return dst[:d], nil
} }
// MaxEncodedLen returns the maximum length of a snappy block, given its // MaxEncodedLen returns the maximum length of a snappy block, given its
@@ -176,7 +176,7 @@ func MaxEncodedLen(srcLen int) int {
// NewWriter returns a new Writer that compresses to w, using the framing // NewWriter returns a new Writer that compresses to w, using the framing
// format described at // format described at
// https://github.com/google/snappy/blob/master/framing_format.txt // https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
func NewWriter(w io.Writer) *Writer { func NewWriter(w io.Writer) *Writer {
return &Writer{ return &Writer{
w: w, w: w,
@@ -226,7 +226,11 @@ func (w *Writer) Write(p []byte) (n int, errRet error) {
// Compress the buffer, discarding the result if the improvement // Compress the buffer, discarding the result if the improvement
// isn't at least 12.5%. // isn't at least 12.5%.
chunkType := uint8(chunkTypeCompressedData) chunkType := uint8(chunkTypeCompressedData)
chunkBody := Encode(w.enc, uncompressed) chunkBody, err := Encode(w.enc, uncompressed)
if err != nil {
w.err = err
return n, err
}
if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 { if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 {
chunkType, chunkBody = chunkTypeUncompressedData, uncompressed chunkType, chunkBody = chunkTypeUncompressedData, uncompressed
} }
@@ -240,11 +244,11 @@ func (w *Writer) Write(p []byte) (n int, errRet error) {
w.buf[5] = uint8(checksum >> 8) w.buf[5] = uint8(checksum >> 8)
w.buf[6] = uint8(checksum >> 16) w.buf[6] = uint8(checksum >> 16)
w.buf[7] = uint8(checksum >> 24) w.buf[7] = uint8(checksum >> 24)
if _, err := w.w.Write(w.buf[:]); err != nil { if _, err = w.w.Write(w.buf[:]); err != nil {
w.err = err w.err = err
return n, err return n, err
} }
if _, err := w.w.Write(chunkBody); err != nil { if _, err = w.w.Write(chunkBody); err != nil {
w.err = err w.err = err
return n, err return n, err
} }
@@ -5,7 +5,7 @@
// Package snappy implements the snappy block-based compression format. // Package snappy implements the snappy block-based compression format.
// It aims for very high speeds and reasonable compression. // It aims for very high speeds and reasonable compression.
// //
// The C++ snappy implementation is at https://github.com/google/snappy // The C++ snappy implementation is at http://code.google.com/p/snappy/
package snappy package snappy
import ( import (
@@ -46,7 +46,7 @@ const (
chunkHeaderSize = 4 chunkHeaderSize = 4
magicChunk = "\xff\x06\x00\x00" + magicBody magicChunk = "\xff\x06\x00\x00" + magicBody
magicBody = "sNaPpY" magicBody = "sNaPpY"
// https://github.com/google/snappy/blob/master/framing_format.txt says // https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt says
// that "the uncompressed data in a chunk must be no longer than 65536 bytes". // that "the uncompressed data in a chunk must be no longer than 65536 bytes".
maxUncompressedChunkLen = 65536 maxUncompressedChunkLen = 65536
) )
@@ -61,7 +61,7 @@ const (
var crcTable = crc32.MakeTable(crc32.Castagnoli) var crcTable = crc32.MakeTable(crc32.Castagnoli)
// crc implements the checksum specified in section 3 of // crc implements the checksum specified in section 3 of
// https://github.com/google/snappy/blob/master/framing_format.txt // https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt
func crc(b []byte) uint32 { func crc(b []byte) uint32 {
c := crc32.Update(0, crcTable, b) c := crc32.Update(0, crcTable, b)
return uint32(c>>15|c<<17) + 0xa282ead8 return uint32(c>>15|c<<17) + 0xa282ead8
@@ -24,7 +24,11 @@ var (
) )
func roundtrip(b, ebuf, dbuf []byte) error { func roundtrip(b, ebuf, dbuf []byte) error {
d, err := Decode(dbuf, Encode(ebuf, b)) e, err := Encode(ebuf, b)
if err != nil {
return fmt.Errorf("encoding error: %v", err)
}
d, err := Decode(dbuf, e)
if err != nil { if err != nil {
return fmt.Errorf("decoding error: %v", err) return fmt.Errorf("decoding error: %v", err)
} }
@@ -78,26 +82,6 @@ func TestSmallRegular(t *testing.T) {
} }
} }
func TestInvalidVarint(t *testing.T) {
data := []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
if _, err := DecodedLen(data); err != ErrCorrupt {
t.Errorf("DecodedLen: got %v, want ErrCorrupt", err)
}
if _, err := Decode(nil, data); err != ErrCorrupt {
t.Errorf("Decode: got %v, want ErrCorrupt", err)
}
// The encoded varint overflows 32 bits
data = []byte("\xff\xff\xff\xff\xff\x00")
if _, err := DecodedLen(data); err != ErrCorrupt {
t.Errorf("DecodedLen: got %v, want ErrCorrupt", err)
}
if _, err := Decode(nil, data); err != ErrCorrupt {
t.Errorf("Decode: got %v, want ErrCorrupt", err)
}
}
func cmp(a, b []byte) error { func cmp(a, b []byte) error {
if len(a) != len(b) { if len(a) != len(b) {
return fmt.Errorf("got %d bytes, want %d", len(a), len(b)) return fmt.Errorf("got %d bytes, want %d", len(a), len(b))
@@ -213,7 +197,10 @@ func TestWriterReset(t *testing.T) {
} }
func benchDecode(b *testing.B, src []byte) { func benchDecode(b *testing.B, src []byte) {
encoded := Encode(nil, src) encoded, err := Encode(nil, src)
if err != nil {
b.Fatal(err)
}
// Bandwidth is in amount of uncompressed data. // Bandwidth is in amount of uncompressed data.
b.SetBytes(int64(len(src))) b.SetBytes(int64(len(src)))
b.ResetTimer() b.ResetTimer()
@@ -235,7 +222,7 @@ func benchEncode(b *testing.B, src []byte) {
func readFile(b testing.TB, filename string) []byte { func readFile(b testing.TB, filename string) []byte {
src, err := ioutil.ReadFile(filename) src, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
b.Skipf("skipping benchmark: %v", err) b.Fatalf("failed reading %s: %s", filename, err)
} }
if len(src) == 0 { if len(src) == 0 {
b.Fatalf("%s has zero length", filename) b.Fatalf("%s has zero length", filename)
@@ -297,14 +284,14 @@ var testFiles = []struct {
// The test data files are present at this canonical URL. // The test data files are present at this canonical URL.
const baseURL = "https://raw.githubusercontent.com/google/snappy/master/testdata/" const baseURL = "https://raw.githubusercontent.com/google/snappy/master/testdata/"
func downloadTestdata(b *testing.B, basename string) (errRet error) { func downloadTestdata(basename string) (errRet error) {
filename := filepath.Join(*testdata, basename) filename := filepath.Join(*testdata, basename)
if stat, err := os.Stat(filename); err == nil && stat.Size() != 0 { if stat, err := os.Stat(filename); err == nil && stat.Size() != 0 {
return nil return nil
} }
if !*download { if !*download {
b.Skipf("test data not found; skipping benchmark without the -download flag") return fmt.Errorf("test data not found; skipping benchmark without the -download flag")
} }
// Download the official snappy C++ implementation reference test data // Download the official snappy C++ implementation reference test data
// files for benchmarking. // files for benchmarking.
@@ -339,7 +326,7 @@ func downloadTestdata(b *testing.B, basename string) (errRet error) {
} }
func benchFile(b *testing.B, n int, decode bool) { func benchFile(b *testing.B, n int, decode bool) {
if err := downloadTestdata(b, testFiles[n].filename); err != nil { if err := downloadTestdata(testFiles[n].filename); err != nil {
b.Fatalf("failed to download testdata: %s", err) b.Fatalf("failed to download testdata: %s", err)
} }
data := readFile(b, filepath.Join(*testdata, testFiles[n].filename)) data := readFile(b, filepath.Join(*testdata, testFiles[n].filename))
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2014-2015 Barracuda Networks, Inc. Copyright (c) 2014 Barracuda Networks, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+8 -13
View File
@@ -6,8 +6,10 @@ Suture
Suture provides Erlang-ish supervisor trees for Go. "Supervisor trees" -> Suture provides Erlang-ish supervisor trees for Go. "Supervisor trees" ->
"sutree" -> "suture" -> holds your code together when it's trying to die. "sutree" -> "suture" -> holds your code together when it's trying to die.
This library has hit maturity, and isn't expected to be changed This is intended to be a production-quality library going into code that I
radically. This can also be imported via gopkg.in/thejerf/suture.v1 . will be very early on the phone tree to support when it goes down. However,
it has not been deployed into something quite that serious yet. (I will
update this statement when that changes.)
It is intended to deal gracefully with the real failure cases that can It is intended to deal gracefully with the real failure cases that can
occur with supervision trees (such as burning all your CPU time endlessly occur with supervision trees (such as burning all your CPU time endlessly
@@ -22,6 +24,10 @@ This module is fully covered with [godoc](http://godoc.org/github.com/thejerf/su
including an example, usage, and everything else you might expect from a including an example, usage, and everything else you might expect from a
README.md on GitHub. (DRY.) README.md on GitHub. (DRY.)
This is not currently tagged with particular git tags for Go as this is
currently considered to be alpha code. As I move this into production and
feel more confident about it, I'll give it relevant tags.
Code Signing Code Signing
------------ ------------
@@ -37,14 +43,3 @@ easily fit into the OTP paradigm. It ought to someday be considered a good
idea to distribute libraries that provide some sort of supervisor tree idea to distribute libraries that provide some sort of supervisor tree
functionality out of the box. It is possible to provide this functionality functionality out of the box. It is possible to provide this functionality
without explicitly depending on the Suture library. without explicitly depending on the Suture library.
Changelog
---------
suture uses semantic versioning.
1. 1.0.0
* Initial release.
2. 1.0.1
* Fixed data race on the .state variable.
+13 -53
View File
@@ -36,13 +36,6 @@ to your Supervisor. Supervisors are also services, so you can create a
tree structure here, depending on the exact combination of restarts tree structure here, depending on the exact combination of restarts
you want to create. you want to create.
As a special case, when adding Supervisors to Supervisors, the "sub"
supervisor will have the "super" supervisor's Log function copied.
This allows you to set one log function on the "top" supervisor, and
have it propagate down to all the sub-supervisors. This also allows
libraries or modules to provide Supervisors without having to commit
their users to a particular logging method.
Finally, as what is probably the last line of your main() function, call Finally, as what is probably the last line of your main() function, call
.Serve() on your top level supervisor. This will start all the services .Serve() on your top level supervisor. This will start all the services
you've defined. you've defined.
@@ -59,7 +52,6 @@ import (
"log" "log"
"math" "math"
"runtime" "runtime"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
) )
@@ -124,6 +116,7 @@ type Supervisor struct {
lastFail time.Time lastFail time.Time
failures float64 failures float64
restartQueue []serviceID restartQueue []serviceID
state uint8
serviceCounter serviceID serviceCounter serviceID
control chan supervisorMessage control chan supervisorMessage
resumeTimer <-chan time.Time resumeTimer <-chan time.Time
@@ -133,19 +126,14 @@ type Supervisor struct {
// If you ever come up with some need to get into these, submit a pull // If you ever come up with some need to get into these, submit a pull
// request to make them public and some smidge of justification, and // request to make them public and some smidge of justification, and
// I'll happily do it. // I'll happily do it.
// But since I've now changed the signature on these once, I'm glad I logBadStop func(Service)
// didn't start with them public... :) logFailure func(service Service, currentFailures float64, failureThreshold float64, restarting bool, error interface{}, stacktrace []byte)
logBadStop func(*Supervisor, Service)
logFailure func(supervisor *Supervisor, service Service, currentFailures float64, failureThreshold float64, restarting bool, error interface{}, stacktrace []byte)
logBackoff func(*Supervisor, bool) logBackoff func(*Supervisor, bool)
// avoid a dependency on github.com/thejerf/abtime by just implementing // avoid a dependency on github.com/thejerf/abtime by just implementing
// a minimal chunk. // a minimal chunk.
getNow func() time.Time getNow func() time.Time
getResume func(time.Duration) <-chan time.Time getResume func(time.Duration) <-chan time.Time
sync.Mutex
state uint8
} }
// Spec is used to pass arguments to the New function to create a // Spec is used to pass arguments to the New function to create a
@@ -245,10 +233,10 @@ func New(name string, spec Spec) (s *Supervisor) {
s.resumeTimer = make(chan time.Time) s.resumeTimer = make(chan time.Time)
// set up the default logging handlers // set up the default logging handlers
s.logBadStop = func(supervisor *Supervisor, service Service) { s.logBadStop = func(service Service) {
s.log(fmt.Sprintf("%s: Service %s failed to terminate in a timely manner", serviceName(supervisor), serviceName(service))) s.log(fmt.Sprintf("Service %s failed to terminate in a timely manner", serviceName(service)))
} }
s.logFailure = func(supervisor *Supervisor, service Service, failures float64, threshold float64, restarting bool, err interface{}, st []byte) { s.logFailure = func(service Service, failures float64, threshold float64, restarting bool, err interface{}, st []byte) {
var errString string var errString string
e, canError := err.(error) e, canError := err.(error)
@@ -258,7 +246,7 @@ func New(name string, spec Spec) (s *Supervisor) {
errString = fmt.Sprintf("%#v", err) errString = fmt.Sprintf("%#v", err)
} }
s.log(fmt.Sprintf("%s: Failed service '%s' (%f failures of %f), restarting: %#v, error: %s, stacktrace: %s", serviceName(supervisor), serviceName(service), failures, threshold, restarting, errString, string(st))) s.log(fmt.Sprintf("Failed service '%s' (%f failures of %f), restarting: %#v, error: %s, stacktrace: %s", serviceName(service), failures, threshold, restarting, errString, string(st)))
} }
s.logBackoff = func(s *Supervisor, entering bool) { s.logBackoff = func(s *Supervisor, entering bool) {
if entering { if entering {
@@ -358,25 +346,12 @@ will be started when the supervisor is.
The returned ServiceID may be passed to the Remove method of the Supervisor The returned ServiceID may be passed to the Remove method of the Supervisor
to terminate the service. to terminate the service.
As a special behavior, if the service added is itself a supervisor, the
supervisor being added will copy the Log function from the Supervisor it
is being added to. This allows factoring out providing a Supervisor
from its logging.
*/ */
func (s *Supervisor) Add(service Service) ServiceToken { func (s *Supervisor) Add(service Service) ServiceToken {
if s == nil { if s == nil {
panic("can't add service to nil *suture.Supervisor") panic("can't add service to nil *suture.Supervisor")
} }
if supervisor, isSupervisor := service.(*Supervisor); isSupervisor {
supervisor.logBadStop = s.logBadStop
supervisor.logFailure = s.logFailure
supervisor.logBackoff = s.logBackoff
}
s.Lock()
if s.state == notRunning { if s.state == notRunning {
id := s.serviceCounter id := s.serviceCounter
s.serviceCounter++ s.serviceCounter++
@@ -384,10 +359,8 @@ func (s *Supervisor) Add(service Service) ServiceToken {
s.services[id] = service s.services[id] = service
s.restartQueue = append(s.restartQueue, id) s.restartQueue = append(s.restartQueue, id)
s.Unlock()
return ServiceToken{uint64(s.id)<<32 | uint64(id)} return ServiceToken{uint64(s.id)<<32 | uint64(id)}
} }
s.Unlock()
response := make(chan serviceID) response := make(chan serviceID)
s.control <- addService{service, response} s.control <- addService{service, response}
@@ -414,19 +387,16 @@ func (s *Supervisor) Serve() {
} }
defer func() { defer func() {
s.Lock()
s.state = notRunning s.state = notRunning
s.Unlock()
}() }()
s.Lock()
if s.state != notRunning { if s.state != notRunning {
s.Unlock() // FIXME: Don't explain why I don't need a semaphore, just use one
// This doesn't use a semaphore because it's just a sanity check.
panic("Running a supervisor while it is already running?") panic("Running a supervisor while it is already running?")
} }
s.state = normal s.state = normal
s.Unlock()
// for all the services I currently know about, start them // for all the services I currently know about, start them
for _, id := range s.restartQueue { for _, id := range s.restartQueue {
@@ -481,9 +451,7 @@ func (s *Supervisor) Serve() {
// excessive thrashing // excessive thrashing
// FIXME: Ought to permit some spacing of these functions, rather // FIXME: Ought to permit some spacing of these functions, rather
// than simply hammering through them // than simply hammering through them
s.Lock()
s.state = normal s.state = normal
s.Unlock()
s.failures = 0 s.failures = 0
s.logBackoff(s, false) s.logBackoff(s, false)
for _, id := range s.restartQueue { for _, id := range s.restartQueue {
@@ -510,9 +478,7 @@ func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktra
} }
if s.failures > s.failureThreshold { if s.failures > s.failureThreshold {
s.Lock()
s.state = paused s.state = paused
s.Unlock()
s.logBackoff(s, true) s.logBackoff(s, true)
s.resumeTimer = s.getResume(s.failureBackoff) s.resumeTimer = s.getResume(s.failureBackoff)
} }
@@ -524,20 +490,14 @@ func (s *Supervisor) handleFailedService(id serviceID, err interface{}, stacktra
// It is possible for a service to be no longer monitored // It is possible for a service to be no longer monitored
// by the time we get here. In that case, just ignore it. // by the time we get here. In that case, just ignore it.
if monitored { if monitored {
// this may look dangerous because the state could change, but this if s.state == normal {
// code is only ever run in the one goroutine that is permitted to
// change the state, so nothing else will.
s.Lock()
curState := s.state
s.Unlock()
if curState == normal {
s.runService(failedService, id) s.runService(failedService, id)
s.logFailure(s, failedService, s.failures, s.failureThreshold, true, err, stacktrace) s.logFailure(failedService, s.failures, s.failureThreshold, true, err, stacktrace)
} else { } else {
// FIXME: When restarting, check that the service still // FIXME: When restarting, check that the service still
// exists (it may have been stopped in the meantime) // exists (it may have been stopped in the meantime)
s.restartQueue = append(s.restartQueue, id) s.restartQueue = append(s.restartQueue, id)
s.logFailure(s, failedService, s.failures, s.failureThreshold, false, err, stacktrace) s.logFailure(failedService, s.failures, s.failureThreshold, false, err, stacktrace)
} }
} }
} }
@@ -576,7 +536,7 @@ func (s *Supervisor) removeService(id serviceID) {
case <-successChan: case <-successChan:
// Life is good! // Life is good!
case <-failChan: case <-failChan:
s.logBadStop(s, service) s.logBadStop(service)
} }
}() }()
} }
+1 -1
View File
@@ -17,7 +17,7 @@ func (i *Incrementor) Serve() {
for { for {
select { select {
case i.next <- i.current: case i.next <- i.current:
i.current++ i.current += 1
case <-i.stop: case <-i.stop:
// We sync here just to guarantee the output of "Stopping the service", // We sync here just to guarantee the output of "Stopping the service",
// so this passes the test reliably. // so this passes the test reliably.
+10 -34
View File
@@ -77,7 +77,7 @@ func TestFailures(t *testing.T) {
// to avoid deadlocks during shutdown, we have to not try to send // to avoid deadlocks during shutdown, we have to not try to send
// things out on channels while we're shutting down (this undoes the // things out on channels while we're shutting down (this undoes the
// logFailure overide about 25 lines down) // logFailure overide about 25 lines down)
s.logFailure = func(*Supervisor, Service, float64, float64, bool, interface{}, []byte) {} s.logFailure = func(Service, float64, float64, bool, interface{}, []byte) {}
s.Stop() s.Stop()
}() }()
s.sync() s.sync()
@@ -102,7 +102,7 @@ func TestFailures(t *testing.T) {
failNotify := make(chan bool) failNotify := make(chan bool)
// use this to synchronize on here // use this to synchronize on here
s.logFailure = func(supervisor *Supervisor, s Service, cf float64, ft float64, r bool, error interface{}, stacktrace []byte) { s.logFailure = func(s Service, cf float64, ft float64, r bool, error interface{}, stacktrace []byte) {
failNotify <- r failNotify <- r
} }
@@ -276,8 +276,8 @@ func TestDefaultLogging(t *testing.T) {
serviceName(&BarelyService{}) serviceName(&BarelyService{})
s.logBadStop(s, service) s.logBadStop(service)
s.logFailure(s, service, 1, 1, true, errors.New("test error"), []byte{}) s.logFailure(service, 1, 1, true, errors.New("test error"), []byte{})
s.Stop() s.Stop()
} }
@@ -289,17 +289,9 @@ func TestNestedSupervisors(t *testing.T) {
super2 := NewSimple("Nested5") super2 := NewSimple("Nested5")
service := NewService("Service5") service := NewService("Service5")
super2.logBadStop = func(*Supervisor, Service) {
panic("Failed to copy logBadStop")
}
super1.Add(super2) super1.Add(super2)
super2.Add(service) super2.Add(service)
// test the functions got copied from super1; if this panics, it didn't
// get copied
super2.logBadStop(super2, service)
go super1.Serve() go super1.Serve()
super1.sync() super1.sync()
@@ -348,7 +340,7 @@ func TestStoppingStillWorksWithHungServices(t *testing.T) {
return resumeChan return resumeChan
} }
failNotify := make(chan struct{}) failNotify := make(chan struct{})
s.logBadStop = func(supervisor *Supervisor, s Service) { s.logBadStop = func(s Service) {
failNotify <- struct{}{} failNotify <- struct{}{}
} }
@@ -446,7 +438,7 @@ func TestFailingSupervisors(t *testing.T) {
} }
failNotify := make(chan string) failNotify := make(chan string)
// use this to synchronize on here // use this to synchronize on here
s1.logFailure = func(supervisor *Supervisor, s Service, cf float64, ft float64, r bool, error interface{}, stacktrace []byte) { s1.logFailure = func(s Service, cf float64, ft float64, r bool, error interface{}, stacktrace []byte) {
failNotify <- fmt.Sprintf("%s", s) failNotify <- fmt.Sprintf("%s", s)
} }
@@ -478,22 +470,6 @@ func TestNilSupervisorAdd(t *testing.T) {
s.Add(s) s.Add(s)
} }
// https://github.com/thejerf/suture/issues/11
//
// The purpose of this test is to verify that it does not cause data races,
// so there are no obvious assertions.
func TestIssue11(t *testing.T) {
t.Parallel()
s := NewSimple("main")
s.ServeBackground()
subsuper := NewSimple("sub")
s.Add(subsuper)
subsuper.Add(NewService("may cause data race"))
}
// http://golangtutorials.blogspot.com/2011/10/gotest-unit-testing-and-benchmarking-go.html // http://golangtutorials.blogspot.com/2011/10/gotest-unit-testing-and-benchmarking-go.html
// claims test function are run in the same order as the source file... // claims test function are run in the same order as the source file...
// I'm not sure if this is part of the contract, though. Especially in the // I'm not sure if this is part of the contract, though. Especially in the
@@ -525,7 +501,7 @@ func (s *FailableService) Serve() {
everMultistarted = true everMultistarted = true
panic("Multi-started the same service! " + s.name) panic("Multi-started the same service! " + s.name)
} }
s.existing++ s.existing += 1
s.started <- true s.started <- true
@@ -538,13 +514,13 @@ func (s *FailableService) Serve() {
case Happy: case Happy:
// Do nothing on purpose. Life is good! // Do nothing on purpose. Life is good!
case Fail: case Fail:
s.existing-- s.existing -= 1
if useStopChan { if useStopChan {
s.stop <- true s.stop <- true
} }
return return
case Panic: case Panic:
s.existing-- s.existing -= 1
panic("Panic!") panic("Panic!")
case Hang: case Hang:
// or more specifically, "hang until I release you" // or more specifically, "hang until I release you"
@@ -553,7 +529,7 @@ func (s *FailableService) Serve() {
useStopChan = true useStopChan = true
} }
case <-s.shutdown: case <-s.shutdown:
s.existing-- s.existing -= 1
if useStopChan { if useStopChan {
s.stop <- true s.stop <- true
} }
-181
View File
@@ -1,181 +0,0 @@
// go generate gen.go
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA).
package iana // import "golang.org/x/net/internal/iana"
// Differentiated Services Field Codepoints (DSCP), Updated: 2013-06-25
const (
DiffServCS0 = 0x0 // CS0
DiffServCS1 = 0x20 // CS1
DiffServCS2 = 0x40 // CS2
DiffServCS3 = 0x60 // CS3
DiffServCS4 = 0x80 // CS4
DiffServCS5 = 0xa0 // CS5
DiffServCS6 = 0xc0 // CS6
DiffServCS7 = 0xe0 // CS7
DiffServAF11 = 0x28 // AF11
DiffServAF12 = 0x30 // AF12
DiffServAF13 = 0x38 // AF13
DiffServAF21 = 0x48 // AF21
DiffServAF22 = 0x50 // AF22
DiffServAF23 = 0x58 // AF23
DiffServAF31 = 0x68 // AF31
DiffServAF32 = 0x70 // AF32
DiffServAF33 = 0x78 // AF33
DiffServAF41 = 0x88 // AF41
DiffServAF42 = 0x90 // AF42
DiffServAF43 = 0x98 // AF43
DiffServEFPHB = 0xb8 // EF PHB
DiffServVOICEADMIT = 0xb0 // VOICE-ADMIT
)
// IPv4 TOS Byte and IPv6 Traffic Class Octet, Updated: 2001-09-06
const (
NotECNTransport = 0x0 // Not-ECT (Not ECN-Capable Transport)
ECNTransport1 = 0x1 // ECT(1) (ECN-Capable Transport(1))
ECNTransport0 = 0x2 // ECT(0) (ECN-Capable Transport(0))
CongestionExperienced = 0x3 // CE (Congestion Experienced)
)
// Protocol Numbers, Updated: 2015-06-23
const (
ProtocolIP = 0 // IPv4 encapsulation, pseudo protocol number
ProtocolHOPOPT = 0 // IPv6 Hop-by-Hop Option
ProtocolICMP = 1 // Internet Control Message
ProtocolIGMP = 2 // Internet Group Management
ProtocolGGP = 3 // Gateway-to-Gateway
ProtocolIPv4 = 4 // IPv4 encapsulation
ProtocolST = 5 // Stream
ProtocolTCP = 6 // Transmission Control
ProtocolCBT = 7 // CBT
ProtocolEGP = 8 // Exterior Gateway Protocol
ProtocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
ProtocolBBNRCCMON = 10 // BBN RCC Monitoring
ProtocolNVPII = 11 // Network Voice Protocol
ProtocolPUP = 12 // PUP
ProtocolARGUS = 13 // ARGUS
ProtocolEMCON = 14 // EMCON
ProtocolXNET = 15 // Cross Net Debugger
ProtocolCHAOS = 16 // Chaos
ProtocolUDP = 17 // User Datagram
ProtocolMUX = 18 // Multiplexing
ProtocolDCNMEAS = 19 // DCN Measurement Subsystems
ProtocolHMP = 20 // Host Monitoring
ProtocolPRM = 21 // Packet Radio Measurement
ProtocolXNSIDP = 22 // XEROX NS IDP
ProtocolTRUNK1 = 23 // Trunk-1
ProtocolTRUNK2 = 24 // Trunk-2
ProtocolLEAF1 = 25 // Leaf-1
ProtocolLEAF2 = 26 // Leaf-2
ProtocolRDP = 27 // Reliable Data Protocol
ProtocolIRTP = 28 // Internet Reliable Transaction
ProtocolISOTP4 = 29 // ISO Transport Protocol Class 4
ProtocolNETBLT = 30 // Bulk Data Transfer Protocol
ProtocolMFENSP = 31 // MFE Network Services Protocol
ProtocolMERITINP = 32 // MERIT Internodal Protocol
ProtocolDCCP = 33 // Datagram Congestion Control Protocol
Protocol3PC = 34 // Third Party Connect Protocol
ProtocolIDPR = 35 // Inter-Domain Policy Routing Protocol
ProtocolXTP = 36 // XTP
ProtocolDDP = 37 // Datagram Delivery Protocol
ProtocolIDPRCMTP = 38 // IDPR Control Message Transport Proto
ProtocolTPPP = 39 // TP++ Transport Protocol
ProtocolIL = 40 // IL Transport Protocol
ProtocolIPv6 = 41 // IPv6 encapsulation
ProtocolSDRP = 42 // Source Demand Routing Protocol
ProtocolIPv6Route = 43 // Routing Header for IPv6
ProtocolIPv6Frag = 44 // Fragment Header for IPv6
ProtocolIDRP = 45 // Inter-Domain Routing Protocol
ProtocolRSVP = 46 // Reservation Protocol
ProtocolGRE = 47 // Generic Routing Encapsulation
ProtocolDSR = 48 // Dynamic Source Routing Protocol
ProtocolBNA = 49 // BNA
ProtocolESP = 50 // Encap Security Payload
ProtocolAH = 51 // Authentication Header
ProtocolINLSP = 52 // Integrated Net Layer Security TUBA
ProtocolNARP = 54 // NBMA Address Resolution Protocol
ProtocolMOBILE = 55 // IP Mobility
ProtocolTLSP = 56 // Transport Layer Security Protocol using Kryptonet key management
ProtocolSKIP = 57 // SKIP
ProtocolIPv6ICMP = 58 // ICMP for IPv6
ProtocolIPv6NoNxt = 59 // No Next Header for IPv6
ProtocolIPv6Opts = 60 // Destination Options for IPv6
ProtocolCFTP = 62 // CFTP
ProtocolSATEXPAK = 64 // SATNET and Backroom EXPAK
ProtocolKRYPTOLAN = 65 // Kryptolan
ProtocolRVD = 66 // MIT Remote Virtual Disk Protocol
ProtocolIPPC = 67 // Internet Pluribus Packet Core
ProtocolSATMON = 69 // SATNET Monitoring
ProtocolVISA = 70 // VISA Protocol
ProtocolIPCV = 71 // Internet Packet Core Utility
ProtocolCPNX = 72 // Computer Protocol Network Executive
ProtocolCPHB = 73 // Computer Protocol Heart Beat
ProtocolWSN = 74 // Wang Span Network
ProtocolPVP = 75 // Packet Video Protocol
ProtocolBRSATMON = 76 // Backroom SATNET Monitoring
ProtocolSUNND = 77 // SUN ND PROTOCOL-Temporary
ProtocolWBMON = 78 // WIDEBAND Monitoring
ProtocolWBEXPAK = 79 // WIDEBAND EXPAK
ProtocolISOIP = 80 // ISO Internet Protocol
ProtocolVMTP = 81 // VMTP
ProtocolSECUREVMTP = 82 // SECURE-VMTP
ProtocolVINES = 83 // VINES
ProtocolTTP = 84 // Transaction Transport Protocol
ProtocolIPTM = 84 // Internet Protocol Traffic Manager
ProtocolNSFNETIGP = 85 // NSFNET-IGP
ProtocolDGP = 86 // Dissimilar Gateway Protocol
ProtocolTCF = 87 // TCF
ProtocolEIGRP = 88 // EIGRP
ProtocolOSPFIGP = 89 // OSPFIGP
ProtocolSpriteRPC = 90 // Sprite RPC Protocol
ProtocolLARP = 91 // Locus Address Resolution Protocol
ProtocolMTP = 92 // Multicast Transport Protocol
ProtocolAX25 = 93 // AX.25 Frames
ProtocolIPIP = 94 // IP-within-IP Encapsulation Protocol
ProtocolSCCSP = 96 // Semaphore Communications Sec. Pro.
ProtocolETHERIP = 97 // Ethernet-within-IP Encapsulation
ProtocolENCAP = 98 // Encapsulation Header
ProtocolGMTP = 100 // GMTP
ProtocolIFMP = 101 // Ipsilon Flow Management Protocol
ProtocolPNNI = 102 // PNNI over IP
ProtocolPIM = 103 // Protocol Independent Multicast
ProtocolARIS = 104 // ARIS
ProtocolSCPS = 105 // SCPS
ProtocolQNX = 106 // QNX
ProtocolAN = 107 // Active Networks
ProtocolIPComp = 108 // IP Payload Compression Protocol
ProtocolSNP = 109 // Sitara Networks Protocol
ProtocolCompaqPeer = 110 // Compaq Peer Protocol
ProtocolIPXinIP = 111 // IPX in IP
ProtocolVRRP = 112 // Virtual Router Redundancy Protocol
ProtocolPGM = 113 // PGM Reliable Transport Protocol
ProtocolL2TP = 115 // Layer Two Tunneling Protocol
ProtocolDDX = 116 // D-II Data Exchange (DDX)
ProtocolIATP = 117 // Interactive Agent Transfer Protocol
ProtocolSTP = 118 // Schedule Transfer Protocol
ProtocolSRP = 119 // SpectraLink Radio Protocol
ProtocolUTI = 120 // UTI
ProtocolSMP = 121 // Simple Message Protocol
ProtocolPTP = 123 // Performance Transparency Protocol
ProtocolISIS = 124 // ISIS over IPv4
ProtocolFIRE = 125 // FIRE
ProtocolCRTP = 126 // Combat Radio Transport Protocol
ProtocolCRUDP = 127 // Combat Radio User Datagram
ProtocolSSCOPMCE = 128 // SSCOPMCE
ProtocolIPLT = 129 // IPLT
ProtocolSPS = 130 // Secure Packet Shield
ProtocolPIPE = 131 // Private IP Encapsulation within IP
ProtocolSCTP = 132 // Stream Control Transmission Protocol
ProtocolFC = 133 // Fibre Channel
ProtocolRSVPE2EIGNORE = 134 // RSVP-E2E-IGNORE
ProtocolMobilityHeader = 135 // Mobility Header
ProtocolUDPLite = 136 // UDPLite
ProtocolMPLSinIP = 137 // MPLS-in-IP
ProtocolMANET = 138 // MANET Protocols
ProtocolHIP = 139 // Host Identity Protocol
ProtocolShim6 = 140 // Shim6 Protocol
ProtocolWESP = 141 // Wrapped Encapsulating Security Payload
ProtocolROHC = 142 // Robust Header Compression
ProtocolReserved = 255 // Reserved
)
-293
View File
@@ -1,293 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build ignore
//go:generate go run gen.go
// This program generates internet protocol constants and tables by
// reading IANA protocol registries.
package main
import (
"bytes"
"encoding/xml"
"fmt"
"go/format"
"io"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
)
var registries = []struct {
url string
parse func(io.Writer, io.Reader) error
}{
{
"http://www.iana.org/assignments/dscp-registry/dscp-registry.xml",
parseDSCPRegistry,
},
{
"http://www.iana.org/assignments/ipv4-tos-byte/ipv4-tos-byte.xml",
parseTOSTCByte,
},
{
"http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml",
parseProtocolNumbers,
},
}
func main() {
var bb bytes.Buffer
fmt.Fprintf(&bb, "// go generate gen.go\n")
fmt.Fprintf(&bb, "// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT\n\n")
fmt.Fprintf(&bb, "// Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA).\n")
fmt.Fprintf(&bb, `package iana // import "golang.org/x/net/internal/iana"`+"\n\n")
for _, r := range registries {
resp, err := http.Get(r.url)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Fprintf(os.Stderr, "got HTTP status code %v for %v\n", resp.StatusCode, r.url)
os.Exit(1)
}
if err := r.parse(&bb, resp.Body); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Fprintf(&bb, "\n")
}
b, err := format.Source(bb.Bytes())
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := ioutil.WriteFile("const.go", b, 0644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func parseDSCPRegistry(w io.Writer, r io.Reader) error {
dec := xml.NewDecoder(r)
var dr dscpRegistry
if err := dec.Decode(&dr); err != nil {
return err
}
drs := dr.escape()
fmt.Fprintf(w, "// %s, Updated: %s\n", dr.Title, dr.Updated)
fmt.Fprintf(w, "const (\n")
for _, dr := range drs {
fmt.Fprintf(w, "DiffServ%s = %#x", dr.Name, dr.Value)
fmt.Fprintf(w, "// %s\n", dr.OrigName)
}
fmt.Fprintf(w, ")\n")
return nil
}
type dscpRegistry struct {
XMLName xml.Name `xml:"registry"`
Title string `xml:"title"`
Updated string `xml:"updated"`
Note string `xml:"note"`
RegTitle string `xml:"registry>title"`
PoolRecords []struct {
Name string `xml:"name"`
Space string `xml:"space"`
} `xml:"registry>record"`
Records []struct {
Name string `xml:"name"`
Space string `xml:"space"`
} `xml:"registry>registry>record"`
}
type canonDSCPRecord struct {
OrigName string
Name string
Value int
}
func (drr *dscpRegistry) escape() []canonDSCPRecord {
drs := make([]canonDSCPRecord, len(drr.Records))
sr := strings.NewReplacer(
"+", "",
"-", "",
"/", "",
".", "",
" ", "",
)
for i, dr := range drr.Records {
s := strings.TrimSpace(dr.Name)
drs[i].OrigName = s
drs[i].Name = sr.Replace(s)
n, err := strconv.ParseUint(dr.Space, 2, 8)
if err != nil {
continue
}
drs[i].Value = int(n) << 2
}
return drs
}
func parseTOSTCByte(w io.Writer, r io.Reader) error {
dec := xml.NewDecoder(r)
var ttb tosTCByte
if err := dec.Decode(&ttb); err != nil {
return err
}
trs := ttb.escape()
fmt.Fprintf(w, "// %s, Updated: %s\n", ttb.Title, ttb.Updated)
fmt.Fprintf(w, "const (\n")
for _, tr := range trs {
fmt.Fprintf(w, "%s = %#x", tr.Keyword, tr.Value)
fmt.Fprintf(w, "// %s\n", tr.OrigKeyword)
}
fmt.Fprintf(w, ")\n")
return nil
}
type tosTCByte struct {
XMLName xml.Name `xml:"registry"`
Title string `xml:"title"`
Updated string `xml:"updated"`
Note string `xml:"note"`
RegTitle string `xml:"registry>title"`
Records []struct {
Binary string `xml:"binary"`
Keyword string `xml:"keyword"`
} `xml:"registry>record"`
}
type canonTOSTCByteRecord struct {
OrigKeyword string
Keyword string
Value int
}
func (ttb *tosTCByte) escape() []canonTOSTCByteRecord {
trs := make([]canonTOSTCByteRecord, len(ttb.Records))
sr := strings.NewReplacer(
"Capable", "",
"(", "",
")", "",
"+", "",
"-", "",
"/", "",
".", "",
" ", "",
)
for i, tr := range ttb.Records {
s := strings.TrimSpace(tr.Keyword)
trs[i].OrigKeyword = s
ss := strings.Split(s, " ")
if len(ss) > 1 {
trs[i].Keyword = strings.Join(ss[1:], " ")
} else {
trs[i].Keyword = ss[0]
}
trs[i].Keyword = sr.Replace(trs[i].Keyword)
n, err := strconv.ParseUint(tr.Binary, 2, 8)
if err != nil {
continue
}
trs[i].Value = int(n)
}
return trs
}
func parseProtocolNumbers(w io.Writer, r io.Reader) error {
dec := xml.NewDecoder(r)
var pn protocolNumbers
if err := dec.Decode(&pn); err != nil {
return err
}
prs := pn.escape()
prs = append([]canonProtocolRecord{{
Name: "IP",
Descr: "IPv4 encapsulation, pseudo protocol number",
Value: 0,
}}, prs...)
fmt.Fprintf(w, "// %s, Updated: %s\n", pn.Title, pn.Updated)
fmt.Fprintf(w, "const (\n")
for _, pr := range prs {
if pr.Name == "" {
continue
}
fmt.Fprintf(w, "Protocol%s = %d", pr.Name, pr.Value)
s := pr.Descr
if s == "" {
s = pr.OrigName
}
fmt.Fprintf(w, "// %s\n", s)
}
fmt.Fprintf(w, ")\n")
return nil
}
type protocolNumbers struct {
XMLName xml.Name `xml:"registry"`
Title string `xml:"title"`
Updated string `xml:"updated"`
RegTitle string `xml:"registry>title"`
Note string `xml:"registry>note"`
Records []struct {
Value string `xml:"value"`
Name string `xml:"name"`
Descr string `xml:"description"`
} `xml:"registry>record"`
}
type canonProtocolRecord struct {
OrigName string
Name string
Descr string
Value int
}
func (pn *protocolNumbers) escape() []canonProtocolRecord {
prs := make([]canonProtocolRecord, len(pn.Records))
sr := strings.NewReplacer(
"-in-", "in",
"-within-", "within",
"-over-", "over",
"+", "P",
"-", "",
"/", "",
".", "",
" ", "",
)
for i, pr := range pn.Records {
if strings.Contains(pr.Name, "Deprecated") ||
strings.Contains(pr.Name, "deprecated") {
continue
}
prs[i].OrigName = pr.Name
s := strings.TrimSpace(pr.Name)
switch pr.Name {
case "ISIS over IPv4":
prs[i].Name = "ISIS"
case "manet":
prs[i].Name = "MANET"
default:
prs[i].Name = sr.Replace(s)
}
ss := strings.Split(pr.Descr, "\n")
for i := range ss {
ss[i] = strings.TrimSpace(ss[i])
}
if len(ss) > 1 {
prs[i].Descr = strings.Join(ss, " ")
} else {
prs[i].Descr = ss[0]
}
prs[i].Value, _ = strconv.Atoi(pr.Value)
}
return prs
}
-92
View File
@@ -1,92 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ipv6
import (
"errors"
"fmt"
"net"
"sync"
)
var (
errMissingAddress = errors.New("missing address")
errInvalidConnType = errors.New("invalid conn type")
errNoSuchInterface = errors.New("no such interface")
)
// Note that RFC 3542 obsoletes RFC 2292 but OS X Snow Leopard and the
// former still support RFC 2292 only. Please be aware that almost
// all protocol implementations prohibit using a combination of RFC
// 2292 and RFC 3542 for some practical reasons.
type rawOpt struct {
sync.RWMutex
cflags ControlFlags
}
func (c *rawOpt) set(f ControlFlags) { c.cflags |= f }
func (c *rawOpt) clear(f ControlFlags) { c.cflags &^= f }
func (c *rawOpt) isset(f ControlFlags) bool { return c.cflags&f != 0 }
// A ControlFlags represents per packet basis IP-level socket option
// control flags.
type ControlFlags uint
const (
FlagTrafficClass ControlFlags = 1 << iota // pass the traffic class on the received packet
FlagHopLimit // pass the hop limit on the received packet
FlagSrc // pass the source address on the received packet
FlagDst // pass the destination address on the received packet
FlagInterface // pass the interface index on the received packet
FlagPathMTU // pass the path MTU on the received packet path
)
const flagPacketInfo = FlagDst | FlagInterface
// A ControlMessage represents per packet basis IP-level socket
// options.
type ControlMessage struct {
// Receiving socket options: SetControlMessage allows to
// receive the options from the protocol stack using ReadFrom
// method of PacketConn.
//
// Specifying socket options: ControlMessage for WriteTo
// method of PacketConn allows to send the options to the
// protocol stack.
//
TrafficClass int // traffic class, must be 1 <= value <= 255 when specifying
HopLimit int // hop limit, must be 1 <= value <= 255 when specifying
Src net.IP // source address, specifying only
Dst net.IP // destination address, receiving only
IfIndex int // interface index, must be 1 <= value when specifying
NextHop net.IP // next hop address, specifying only
MTU int // path MTU, receiving only
}
func (cm *ControlMessage) String() string {
if cm == nil {
return "<nil>"
}
return fmt.Sprintf("tclass: %#x, hoplim: %v, src: %v, dst: %v, ifindex: %v, nexthop: %v, mtu: %v", cm.TrafficClass, cm.HopLimit, cm.Src, cm.Dst, cm.IfIndex, cm.NextHop, cm.MTU)
}
// Ancillary data socket options
const (
ctlTrafficClass = iota // header field
ctlHopLimit // header field
ctlPacketInfo // inbound or outbound packet path
ctlNextHop // nexthop
ctlPathMTU // path mtu
ctlMax
)
// A ctlOpt represents a binding for ancillary data socket option.
type ctlOpt struct {
name int // option name, must be equal or greater than 1
length int // option length
marshal func([]byte, *ControlMessage) []byte
parse func(*ControlMessage, []byte)
}
-56
View File
@@ -1,56 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
package ipv6
import (
"syscall"
"unsafe"
"golang.org/x/net/internal/iana"
)
func marshal2292HopLimit(b []byte, cm *ControlMessage) []byte {
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
m.Level = iana.ProtocolIPv6
m.Type = sysIPV6_2292HOPLIMIT
m.SetLen(syscall.CmsgLen(4))
if cm != nil {
data := b[syscall.CmsgLen(0):]
// TODO(mikio): fix potential misaligned memory access
*(*int32)(unsafe.Pointer(&data[:4][0])) = int32(cm.HopLimit)
}
return b[syscall.CmsgSpace(4):]
}
func marshal2292PacketInfo(b []byte, cm *ControlMessage) []byte {
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
m.Level = iana.ProtocolIPv6
m.Type = sysIPV6_2292PKTINFO
m.SetLen(syscall.CmsgLen(sysSizeofInet6Pktinfo))
if cm != nil {
pi := (*sysInet6Pktinfo)(unsafe.Pointer(&b[syscall.CmsgLen(0)]))
if ip := cm.Src.To16(); ip != nil && ip.To4() == nil {
copy(pi.Addr[:], ip)
}
if cm.IfIndex > 0 {
pi.setIfindex(cm.IfIndex)
}
}
return b[syscall.CmsgSpace(sysSizeofInet6Pktinfo):]
}
func marshal2292NextHop(b []byte, cm *ControlMessage) []byte {
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
m.Level = iana.ProtocolIPv6
m.Type = sysIPV6_2292NEXTHOP
m.SetLen(syscall.CmsgLen(sysSizeofSockaddrInet6))
if cm != nil {
sa := (*sysSockaddrInet6)(unsafe.Pointer(&b[syscall.CmsgLen(0)]))
sa.setSockaddr(cm.NextHop, cm.IfIndex)
}
return b[syscall.CmsgSpace(sysSizeofSockaddrInet6):]
}
-103
View File
@@ -1,103 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd linux netbsd openbsd
package ipv6
import (
"syscall"
"unsafe"
"golang.org/x/net/internal/iana"
)
func marshalTrafficClass(b []byte, cm *ControlMessage) []byte {
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
m.Level = iana.ProtocolIPv6
m.Type = sysIPV6_TCLASS
m.SetLen(syscall.CmsgLen(4))
if cm != nil {
data := b[syscall.CmsgLen(0):]
// TODO(mikio): fix potential misaligned memory access
*(*int32)(unsafe.Pointer(&data[:4][0])) = int32(cm.TrafficClass)
}
return b[syscall.CmsgSpace(4):]
}
func parseTrafficClass(cm *ControlMessage, b []byte) {
// TODO(mikio): fix potential misaligned memory access
cm.TrafficClass = int(*(*int32)(unsafe.Pointer(&b[:4][0])))
}
func marshalHopLimit(b []byte, cm *ControlMessage) []byte {
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
m.Level = iana.ProtocolIPv6
m.Type = sysIPV6_HOPLIMIT
m.SetLen(syscall.CmsgLen(4))
if cm != nil {
data := b[syscall.CmsgLen(0):]
// TODO(mikio): fix potential misaligned memory access
*(*int32)(unsafe.Pointer(&data[:4][0])) = int32(cm.HopLimit)
}
return b[syscall.CmsgSpace(4):]
}
func parseHopLimit(cm *ControlMessage, b []byte) {
// TODO(mikio): fix potential misaligned memory access
cm.HopLimit = int(*(*int32)(unsafe.Pointer(&b[:4][0])))
}
func marshalPacketInfo(b []byte, cm *ControlMessage) []byte {
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
m.Level = iana.ProtocolIPv6
m.Type = sysIPV6_PKTINFO
m.SetLen(syscall.CmsgLen(sysSizeofInet6Pktinfo))
if cm != nil {
pi := (*sysInet6Pktinfo)(unsafe.Pointer(&b[syscall.CmsgLen(0)]))
if ip := cm.Src.To16(); ip != nil && ip.To4() == nil {
copy(pi.Addr[:], ip)
}
if cm.IfIndex > 0 {
pi.setIfindex(cm.IfIndex)
}
}
return b[syscall.CmsgSpace(sysSizeofInet6Pktinfo):]
}
func parsePacketInfo(cm *ControlMessage, b []byte) {
pi := (*sysInet6Pktinfo)(unsafe.Pointer(&b[0]))
cm.Dst = pi.Addr[:]
cm.IfIndex = int(pi.Ifindex)
}
func marshalNextHop(b []byte, cm *ControlMessage) []byte {
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
m.Level = iana.ProtocolIPv6
m.Type = sysIPV6_NEXTHOP
m.SetLen(syscall.CmsgLen(sysSizeofSockaddrInet6))
if cm != nil {
sa := (*sysSockaddrInet6)(unsafe.Pointer(&b[syscall.CmsgLen(0)]))
sa.setSockaddr(cm.NextHop, cm.IfIndex)
}
return b[syscall.CmsgSpace(sysSizeofSockaddrInet6):]
}
func parseNextHop(cm *ControlMessage, b []byte) {
}
func marshalPathMTU(b []byte, cm *ControlMessage) []byte {
m := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
m.Level = iana.ProtocolIPv6
m.Type = sysIPV6_PATHMTU
m.SetLen(syscall.CmsgLen(sysSizeofIPv6Mtuinfo))
return b[syscall.CmsgSpace(sysSizeofIPv6Mtuinfo):]
}
func parsePathMTU(cm *ControlMessage, b []byte) {
mi := (*sysIPv6Mtuinfo)(unsafe.Pointer(&b[0]))
cm.Dst = mi.Addr.Addr[:]
cm.IfIndex = int(mi.Addr.Scope_id)
cm.MTU = int(mi.Mtu)
}
-23
View File
@@ -1,23 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build nacl plan9 solaris
package ipv6
func setControlMessage(fd int, opt *rawOpt, cf ControlFlags, on bool) error {
return errOpNoSupport
}
func newControlMessage(opt *rawOpt) (oob []byte) {
return nil
}
func parseControlMessage(b []byte) (*ControlMessage, error) {
return nil, errOpNoSupport
}
func marshalControlMessage(cm *ControlMessage) (oob []byte) {
return nil
}

Some files were not shown because too many files have changed in this diff Show More