Skip to content

Commit 78367a4

Browse files
committed
Add fallback path for mmap
Closes oschwald#163.
1 parent 2a6b0bf commit 78367a4

File tree

4 files changed

+92
-90
lines changed

4 files changed

+92
-90
lines changed

mmap_stub.go

Lines changed: 25 additions & 0 deletions
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+
}

reader.go

Lines changed: 67 additions & 0 deletions
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,70 @@ 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, the database is loaded into memory.
55+
// Use the Close method on the Reader object to return the resources to the system.
56+
func Open(file string) (*Reader, error) {
57+
mapFile, err := os.Open(file)
58+
if err != nil {
59+
return nil, err
60+
}
61+
defer mapFile.Close()
62+
63+
stats, err := mapFile.Stat()
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
size64 := stats.Size()
69+
size := int(size64)
70+
if int64(size) != size64 {
71+
return nil, errors.New("file too large")
72+
}
73+
74+
data, err := mmap(int(mapFile.Fd()), size)
75+
if err != nil {
76+
if errors.Is(err, errors.ErrUnsupported) {
77+
data, err = openFallback(mapFile, size)
78+
if err != nil {
79+
return nil, err
80+
}
81+
return FromBytes(data)
82+
}
83+
return nil, err
84+
}
85+
86+
reader, err := FromBytes(data)
87+
if err != nil {
88+
_ = munmap(data)
89+
return nil, err
90+
}
91+
92+
reader.hasMappedFile = true
93+
runtime.SetFinalizer(reader, (*Reader).Close)
94+
return reader, nil
95+
}
96+
97+
func openFallback(f *os.File, size int) (data []byte, err error) {
98+
data = make([]byte, size)
99+
_, err = io.ReadFull(f, data)
100+
return data, err
101+
}
102+
103+
// Close returns the resources used by the database to the system.
104+
func (r *Reader) Close() error {
105+
var err error
106+
if r.hasMappedFile {
107+
runtime.SetFinalizer(r, nil)
108+
r.hasMappedFile = false
109+
err = munmap(r.buffer)
110+
}
111+
r.buffer = nil
112+
return err
113+
}
114+
48115
// FromBytes takes a byte slice corresponding to a MaxMind DB file and returns
49116
// a Reader structure or an error.
50117
func FromBytes(buffer []byte) (*Reader, error) {

reader_memory.go

Lines changed: 0 additions & 26 deletions
This file was deleted.

reader_mmap.go

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)