Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
ollama
Commits
5b84404c
Commit
5b84404c
authored
Sep 29, 2023
by
Michael Yang
Browse files
handle concurrent requests for the same blobs
parent
8544edca
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
183 additions
and
99 deletions
+183
-99
server/download.go
server/download.go
+183
-99
No files found.
server/download.go
View file @
5b84404c
...
@@ -12,124 +12,107 @@ import (
...
@@ -12,124 +12,107 @@ import (
"os"
"os"
"path/filepath"
"path/filepath"
"strconv"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/jmorganca/ollama/api"
"golang.org/x/sync/errgroup"
"golang.org/x/sync/errgroup"
"github.com/jmorganca/ollama/api"
)
)
type
BlobDownloadPart
struct
{
var
blobDownloadManager
sync
.
Map
Offset
int64
Size
int64
Completed
int64
}
type
downloadOpts
struct
{
type
blobDownload
struct
{
mp
ModelPath
Name
string
digest
string
Digest
string
regOpts
*
RegistryOptions
fn
func
(
api
.
ProgressResponse
)
}
const
maxRetries
=
3
Total
int64
Completed
atomic
.
Int64
// downloadBlob downloads a blob from the registry and stores it in the blobs directory
*
os
.
File
func
downloadBlob
(
ctx
context
.
Context
,
opts
downloadOpts
)
error
{
Parts
[]
*
blobDownloadPart
fp
,
err
:=
GetBlobsPath
(
opts
.
digest
)
if
err
!=
nil
{
return
err
}
fi
,
err
:=
os
.
Stat
(
fp
)
done
chan
struct
{}
switch
{
context
.
CancelFunc
case
errors
.
Is
(
err
,
os
.
ErrNotExist
)
:
refCount
atomic
.
Int32
case
err
!=
nil
:
}
return
err
default
:
opts
.
fn
(
api
.
ProgressResponse
{
Status
:
fmt
.
Sprintf
(
"downloading %s"
,
opts
.
digest
),
Digest
:
opts
.
digest
,
Total
:
fi
.
Size
(),
Completed
:
fi
.
Size
(),
})
return
nil
type
blobDownloadPart
struct
{
}
Offset
int64
Size
int64
Completed
int64
}
f
,
err
:=
os
.
OpenFile
(
fp
+
"-partial"
,
os
.
O_CREATE
|
os
.
O_RDWR
,
0644
)
func
(
b
*
blobDownload
)
Prepare
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
opts
*
RegistryOptions
)
error
{
if
err
!=
nil
{
b
.
done
=
make
(
chan
struct
{},
1
)
return
err
}
defer
f
.
Close
()
partFilePaths
,
err
:=
filepath
.
Glob
(
fp
+
"-partial-*"
)
partFilePaths
,
err
:=
filepath
.
Glob
(
b
.
Name
+
"-partial-*"
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
var
total
,
completed
int64
var
parts
[]
BlobDownloadPart
for
_
,
partFilePath
:=
range
partFilePaths
{
for
_
,
partFilePath
:=
range
partFilePaths
{
var
part
BlobDownloadPart
part
,
err
:=
b
.
readPart
(
partFilePath
)
partFile
,
err
:=
os
.
Open
(
partFilePath
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
defer
partFile
.
Close
()
if
err
:=
json
.
NewDecoder
(
partFile
)
.
Decode
(
&
part
);
err
!=
nil
{
return
err
}
total
+=
part
.
Size
completed
+=
part
.
Completed
parts
=
append
(
parts
,
part
)
b
.
Total
+=
part
.
Size
b
.
Completed
.
Add
(
part
.
Completed
)
b
.
Parts
=
append
(
b
.
Parts
,
part
)
}
}
requestURL
:=
opts
.
mp
.
BaseURL
()
if
len
(
b
.
Parts
)
==
0
{
requestURL
=
requestURL
.
JoinPath
(
"v2"
,
opts
.
mp
.
GetNamespaceRepository
(),
"blobs"
,
opts
.
digest
)
resp
,
err
:=
makeRequest
(
ctx
,
"HEAD"
,
requestURL
,
nil
,
nil
,
opts
)
if
len
(
parts
)
==
0
{
resp
,
err
:=
makeRequest
(
ctx
,
"HEAD"
,
requestURL
,
nil
,
nil
,
opts
.
regOpts
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
defer
resp
.
Body
.
Close
()
defer
resp
.
Body
.
Close
()
total
,
_
=
strconv
.
ParseInt
(
resp
.
Header
.
Get
(
"Content-Length"
),
10
,
64
)
b
.
Total
,
_
=
strconv
.
ParseInt
(
resp
.
Header
.
Get
(
"Content-Length"
),
10
,
64
)
// reserve the file
f
.
Truncate
(
total
)
var
offset
int64
var
offset
int64
var
size
int64
=
64
*
1024
*
1024
var
size
int64
=
64
*
1024
*
1024
for
offset
<
t
otal
{
for
offset
<
b
.
T
otal
{
if
offset
+
size
>
t
otal
{
if
offset
+
size
>
b
.
T
otal
{
size
=
t
otal
-
offset
size
=
b
.
T
otal
-
offset
}
}
parts
=
append
(
parts
,
BlobDownloadPart
{
partName
:=
b
.
Name
+
"-partial-"
+
strconv
.
Itoa
(
len
(
b
.
Parts
))
Offset
:
offset
,
part
:=
blobDownloadPart
{
Offset
:
offset
,
Size
:
size
}
Size
:
size
,
if
err
:=
b
.
writePart
(
partName
,
&
part
);
err
!=
nil
{
})
return
err
}
b
.
Parts
=
append
(
b
.
Parts
,
&
part
)
offset
+=
size
offset
+=
size
}
}
}
}
pw
:=
&
ProgressWriter
{
log
.
Printf
(
"downloading %s in %d part(s)"
,
b
.
Digest
[
7
:
19
],
len
(
b
.
Parts
))
status
:
fmt
.
Sprintf
(
"downloading %s"
,
opts
.
digest
),
return
nil
digest
:
opts
.
digest
,
}
total
:
total
,
completed
:
completed
,
func
(
b
*
blobDownload
)
Run
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
opts
*
RegistryOptions
)
(
err
error
)
{
fn
:
opts
.
fn
,
defer
blobDownloadManager
.
Delete
(
b
.
Digest
)
ctx
,
b
.
CancelFunc
=
context
.
WithCancel
(
ctx
)
b
.
File
,
err
=
os
.
OpenFile
(
b
.
Name
+
"-partial"
,
os
.
O_CREATE
|
os
.
O_RDWR
,
0644
)
if
err
!=
nil
{
return
err
}
}
defer
b
.
Close
()
b
.
Truncate
(
b
.
Total
)
g
,
ctx
:=
errgroup
.
WithContext
(
ctx
)
g
,
ctx
:=
errgroup
.
WithContext
(
ctx
)
g
.
SetLimit
(
64
)
g
.
SetLimit
(
64
)
for
i
:=
range
p
arts
{
for
i
:=
range
b
.
P
arts
{
part
:=
p
arts
[
i
]
part
:=
b
.
P
arts
[
i
]
if
part
.
Completed
==
part
.
Size
{
if
part
.
Completed
==
part
.
Size
{
continue
continue
}
}
...
@@ -137,13 +120,17 @@ func downloadBlob(ctx context.Context, opts downloadOpts) error {
...
@@ -137,13 +120,17 @@ func downloadBlob(ctx context.Context, opts downloadOpts) error {
i
:=
i
i
:=
i
g
.
Go
(
func
()
error
{
g
.
Go
(
func
()
error
{
for
try
:=
0
;
try
<
maxRetries
;
try
++
{
for
try
:=
0
;
try
<
maxRetries
;
try
++
{
if
err
:=
downloadBlobChunk
(
ctx
,
f
,
requestURL
,
parts
,
i
,
pw
,
opts
);
err
!=
nil
{
err
:=
b
.
downloadChunk
(
ctx
,
requestURL
,
i
,
opts
)
log
.
Printf
(
"%s part %d attempt %d failed: %v, retrying"
,
opts
.
digest
[
7
:
19
],
i
,
try
,
err
)
switch
{
case
errors
.
Is
(
err
,
context
.
Canceled
)
:
return
err
case
err
!=
nil
:
log
.
Printf
(
"%s part %d attempt %d failed: %v, retrying"
,
b
.
Digest
[
7
:
19
],
i
,
try
,
err
)
continue
continue
}
default
:
return
nil
return
nil
}
}
}
return
errors
.
New
(
"max retries exceeded"
)
return
errors
.
New
(
"max retries exceeded"
)
})
})
...
@@ -153,52 +140,67 @@ func downloadBlob(ctx context.Context, opts downloadOpts) error {
...
@@ -153,52 +140,67 @@ func downloadBlob(ctx context.Context, opts downloadOpts) error {
return
err
return
err
}
}
if
err
:=
f
.
Close
();
err
!=
nil
{
if
err
:=
b
.
Close
();
err
!=
nil
{
return
err
return
err
}
}
for
i
:=
range
p
arts
{
for
i
:=
range
b
.
P
arts
{
if
err
:=
os
.
Remove
(
f
.
Name
()
+
"-"
+
strconv
.
Itoa
(
i
));
err
!=
nil
{
if
err
:=
os
.
Remove
(
b
.
File
.
Name
()
+
"-"
+
strconv
.
Itoa
(
i
));
err
!=
nil
{
return
err
return
err
}
}
}
}
return
os
.
Rename
(
f
.
Name
(),
fp
)
if
err
:=
os
.
Rename
(
b
.
File
.
Name
(),
b
.
Name
);
err
!=
nil
{
}
func
downloadBlobChunk
(
ctx
context
.
Context
,
f
*
os
.
File
,
requestURL
*
url
.
URL
,
parts
[]
BlobDownloadPart
,
i
int
,
pw
*
ProgressWriter
,
opts
downloadOpts
)
error
{
part
:=
&
parts
[
i
]
partName
:=
f
.
Name
()
+
"-"
+
strconv
.
Itoa
(
i
)
if
err
:=
flushPart
(
partName
,
part
);
err
!=
nil
{
return
err
return
err
}
}
close
(
b
.
done
)
return
nil
}
func
(
b
*
blobDownload
)
downloadChunk
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
i
int
,
opts
*
RegistryOptions
)
error
{
part
:=
b
.
Parts
[
i
]
partName
:=
b
.
File
.
Name
()
+
"-"
+
strconv
.
Itoa
(
i
)
offset
:=
part
.
Offset
+
part
.
Completed
offset
:=
part
.
Offset
+
part
.
Completed
w
:=
io
.
NewOffsetWriter
(
f
,
offset
)
w
:=
io
.
NewOffsetWriter
(
b
.
File
,
offset
)
headers
:=
make
(
http
.
Header
)
headers
:=
make
(
http
.
Header
)
headers
.
Set
(
"Range"
,
fmt
.
Sprintf
(
"bytes=%d-%d"
,
offset
,
part
.
Offset
+
part
.
Size
-
1
))
headers
.
Set
(
"Range"
,
fmt
.
Sprintf
(
"bytes=%d-%d"
,
offset
,
part
.
Offset
+
part
.
Size
-
1
))
resp
,
err
:=
makeRequest
(
ctx
,
"GET"
,
requestURL
,
headers
,
nil
,
opts
.
regOpts
)
resp
,
err
:=
makeRequest
(
ctx
,
"GET"
,
requestURL
,
headers
,
nil
,
opts
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
defer
resp
.
Body
.
Close
()
defer
resp
.
Body
.
Close
()
n
,
err
:=
io
.
Copy
(
w
,
io
.
TeeReader
(
resp
.
Body
,
pw
))
n
,
err
:=
io
.
Copy
(
w
,
io
.
TeeReader
(
resp
.
Body
,
b
))
if
err
!=
nil
&&
!
errors
.
Is
(
err
,
io
.
EOF
)
{
if
err
!=
nil
&&
!
errors
.
Is
(
err
,
io
.
EOF
)
{
// rollback progress
bar
// rollback progress
pw
.
c
ompleted
-=
n
b
.
C
ompleted
.
Add
(
-
n
)
return
err
return
err
}
}
part
.
Completed
+=
n
part
.
Completed
+=
n
return
b
.
writePart
(
partName
,
part
)
}
func
(
b
*
blobDownload
)
readPart
(
partName
string
)
(
*
blobDownloadPart
,
error
)
{
var
part
blobDownloadPart
partFile
,
err
:=
os
.
Open
(
partName
)
if
err
!=
nil
{
return
nil
,
err
}
defer
partFile
.
Close
()
return
flushPart
(
partName
,
part
)
if
err
:=
json
.
NewDecoder
(
partFile
)
.
Decode
(
&
part
);
err
!=
nil
{
return
nil
,
err
}
return
&
part
,
nil
}
}
func
flushPart
(
n
ame
string
,
part
*
B
lobDownloadPart
)
error
{
func
(
b
*
blobDownload
)
writePart
(
partN
ame
string
,
part
*
b
lobDownloadPart
)
error
{
partFile
,
err
:=
os
.
OpenFile
(
n
ame
,
os
.
O_CREATE
|
os
.
O_RDWR
,
0644
)
partFile
,
err
:=
os
.
OpenFile
(
partN
ame
,
os
.
O_CREATE
|
os
.
O_RDWR
|
os
.
O_TRUNC
,
0644
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
...
@@ -206,3 +208,85 @@ func flushPart(name string, part *BlobDownloadPart) error {
...
@@ -206,3 +208,85 @@ func flushPart(name string, part *BlobDownloadPart) error {
return
json
.
NewEncoder
(
partFile
)
.
Encode
(
part
)
return
json
.
NewEncoder
(
partFile
)
.
Encode
(
part
)
}
}
func
(
b
*
blobDownload
)
Write
(
p
[]
byte
)
(
n
int
,
err
error
)
{
n
=
len
(
p
)
b
.
Completed
.
Add
(
int64
(
n
))
return
n
,
nil
}
func
(
b
*
blobDownload
)
Wait
(
ctx
context
.
Context
,
fn
func
(
api
.
ProgressResponse
))
error
{
b
.
refCount
.
Add
(
1
)
ticker
:=
time
.
NewTicker
(
60
*
time
.
Millisecond
)
for
{
select
{
case
<-
ticker
.
C
:
case
<-
ctx
.
Done
()
:
if
b
.
refCount
.
Add
(
-
1
)
==
0
{
b
.
CancelFunc
()
}
return
ctx
.
Err
()
}
fn
(
api
.
ProgressResponse
{
Status
:
fmt
.
Sprintf
(
"downloading %s"
,
b
.
Digest
),
Digest
:
b
.
Digest
,
Total
:
b
.
Total
,
Completed
:
b
.
Completed
.
Load
(),
})
if
b
.
Completed
.
Load
()
>=
b
.
Total
{
<-
b
.
done
return
nil
}
}
}
type
downloadOpts
struct
{
mp
ModelPath
digest
string
regOpts
*
RegistryOptions
fn
func
(
api
.
ProgressResponse
)
}
const
maxRetries
=
3
// downloadBlob downloads a blob from the registry and stores it in the blobs directory
func
downloadBlob
(
ctx
context
.
Context
,
opts
downloadOpts
)
error
{
fp
,
err
:=
GetBlobsPath
(
opts
.
digest
)
if
err
!=
nil
{
return
err
}
fi
,
err
:=
os
.
Stat
(
fp
)
switch
{
case
errors
.
Is
(
err
,
os
.
ErrNotExist
)
:
case
err
!=
nil
:
return
err
default
:
opts
.
fn
(
api
.
ProgressResponse
{
Status
:
fmt
.
Sprintf
(
"downloading %s"
,
opts
.
digest
),
Digest
:
opts
.
digest
,
Total
:
fi
.
Size
(),
Completed
:
fi
.
Size
(),
})
return
nil
}
value
,
ok
:=
blobDownloadManager
.
LoadOrStore
(
opts
.
digest
,
&
blobDownload
{
Name
:
fp
,
Digest
:
opts
.
digest
})
blobDownload
:=
value
.
(
*
blobDownload
)
if
!
ok
{
requestURL
:=
opts
.
mp
.
BaseURL
()
requestURL
=
requestURL
.
JoinPath
(
"v2"
,
opts
.
mp
.
GetNamespaceRepository
(),
"blobs"
,
opts
.
digest
)
if
err
:=
blobDownload
.
Prepare
(
ctx
,
requestURL
,
opts
.
regOpts
);
err
!=
nil
{
return
err
}
go
blobDownload
.
Run
(
context
.
Background
(),
requestURL
,
opts
.
regOpts
)
}
return
blobDownload
.
Wait
(
ctx
,
opts
.
fn
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment