Skip to content

Commit c46529a

Browse files
authored
Merge pull request #166 from database64128/mmap-fallback
Add fallback path for mmap
2 parents 3b6696e + d57a665 commit c46529a

File tree

5 files changed

+125
-92
lines changed

5 files changed

+125
-92
lines changed

mmap_stub.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//go:build appengine || plan9 || js || wasip1 || wasi
2+
3+
package maxminddb
4+
5+
import (
6+
"errors"
7+
)
8+
9+
type mmapUnsupportedError struct{}
10+
11+
func (mmapUnsupportedError) Error() string {
12+
return "mmap is not supported on this platform"
13+
}
14+
15+
func (mmapUnsupportedError) Is(target error) bool {
16+
return target == errors.ErrUnsupported
17+
}
18+
19+
func mmap(_, _ int) (data []byte, err error) {
20+
return nil, mmapUnsupportedError{}
21+
}
22+
23+
func munmap(_ []byte) (err error) {
24+
return mmapUnsupportedError{}
25+
}

mmap_unix.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,36 @@
44
package maxminddb
55

66
import (
7+
"errors"
8+
"os"
9+
710
"golang.org/x/sys/unix"
811
)
912

13+
type mmapENODEVError struct{}
14+
15+
func (mmapENODEVError) Error() string {
16+
return "mmap: the underlying filesystem of the specified file does not support memory mapping"
17+
}
18+
19+
func (mmapENODEVError) Is(target error) bool {
20+
return target == errors.ErrUnsupported
21+
}
22+
1023
func mmap(fd, length int) (data []byte, err error) {
11-
return unix.Mmap(fd, 0, length, unix.PROT_READ, unix.MAP_SHARED)
24+
data, err = unix.Mmap(fd, 0, length, unix.PROT_READ, unix.MAP_SHARED)
25+
if err != nil {
26+
if err == unix.ENODEV {
27+
return nil, mmapENODEVError{}
28+
}
29+
return nil, os.NewSyscallError("mmap", err)
30+
}
31+
return data, nil
1232
}
1333

1434
func munmap(b []byte) (err error) {
15-
return unix.Munmap(b)
35+
if err = unix.Munmap(b); err != nil {
36+
return os.NewSyscallError("munmap", err)
37+
}
38+
return nil
1639
}

reader.go

+75
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import (
55
"bytes"
66
"errors"
77
"fmt"
8+
"io"
89
"net/netip"
10+
"os"
911
"reflect"
12+
"runtime"
1013
)
1114

1215
const dataSectionSeparatorSize = 16
@@ -45,6 +48,78 @@ type Metadata struct {
4548
RecordSize uint `maxminddb:"record_size"`
4649
}
4750

51+
// Open takes a string path to a MaxMind DB file and returns a Reader
52+
// structure or an error. The database file is opened using a memory map
53+
// on supported platforms. On platforms without memory map support, such
54+
// as WebAssembly or Google App Engine, or if the memory map attempt fails
55+
// due to lack of support from the filesystem, the database is loaded into memory.
56+
// Use the Close method on the Reader object to return the resources to the system.
57+
func Open(file string) (*Reader, error) {
58+
mapFile, err := os.Open(file)
59+
if err != nil {
60+
return nil, err
61+
}
62+
defer mapFile.Close()
63+
64+
stats, err := mapFile.Stat()
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
size64 := stats.Size()
70+
// mmapping an empty file returns -EINVAL on Unix platforms,
71+
// and ERROR_FILE_INVALID on Windows.
72+
if size64 == 0 {
73+
return nil, errors.New("file is empty")
74+
}
75+
76+
size := int(size64)
77+
// Check for overflow.
78+
if int64(size) != size64 {
79+
return nil, errors.New("file too large")
80+
}
81+
82+
data, err := mmap(int(mapFile.Fd()), size)
83+
if err != nil {
84+
if errors.Is(err, errors.ErrUnsupported) {
85+
data, err = openFallback(mapFile, size)
86+
if err != nil {
87+
return nil, err
88+
}
89+
return FromBytes(data)
90+
}
91+
return nil, err
92+
}
93+
94+
reader, err := FromBytes(data)
95+
if err != nil {
96+
_ = munmap(data)
97+
return nil, err
98+
}
99+
100+
reader.hasMappedFile = true
101+
runtime.SetFinalizer(reader, (*Reader).Close)
102+
return reader, nil
103+
}
104+
105+
func openFallback(f *os.File, size int) (data []byte, err error) {
106+
data = make([]byte, size)
107+
_, err = io.ReadFull(f, data)
108+
return data, err
109+
}
110+
111+
// Close returns the resources used by the database to the system.
112+
func (r *Reader) Close() error {
113+
var err error
114+
if r.hasMappedFile {
115+
runtime.SetFinalizer(r, nil)
116+
r.hasMappedFile = false
117+
err = munmap(r.buffer)
118+
}
119+
r.buffer = nil
120+
return err
121+
}
122+
48123
// FromBytes takes a byte slice corresponding to a MaxMind DB file and returns
49124
// a Reader structure or an error.
50125
func FromBytes(buffer []byte) (*Reader, error) {

reader_memory.go

-26
This file was deleted.

reader_mmap.go

-64
This file was deleted.

0 commit comments

Comments
 (0)