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
"vscode:/vscode.git/clone" did not exist on "8c16529b86f8061c8a44f9e35bb5482491a43876"
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 (
"os"
"path/filepath"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/jmorganca/ollama/api"
"golang.org/x/sync/errgroup"
"github.com/jmorganca/ollama/api"
)
type
BlobDownloadPart
struct
{
Offset
int64
Size
int64
Completed
int64
}
var
blobDownloadManager
sync
.
Map
type
downloadOpts
struct
{
mp
ModelPath
digest
string
regOpts
*
RegistryOptions
fn
func
(
api
.
ProgressResponse
)
}
type
blobDownload
struct
{
Name
string
Digest
string
const
maxRetries
=
3
Total
int64
Completed
atomic
.
Int64
// 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
}
*
os
.
File
Parts
[]
*
blobDownloadPart
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
(),
})
done
chan
struct
{}
context
.
CancelFunc
refCount
atomic
.
Int32
}
return
nil
}
type
blobDownloadPart
struct
{
Offset
int64
Size
int64
Completed
int64
}
f
,
err
:=
os
.
OpenFile
(
fp
+
"-partial"
,
os
.
O_CREATE
|
os
.
O_RDWR
,
0644
)
if
err
!=
nil
{
return
err
}
defer
f
.
Close
()
func
(
b
*
blobDownload
)
Prepare
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
opts
*
RegistryOptions
)
error
{
b
.
done
=
make
(
chan
struct
{},
1
)
partFilePaths
,
err
:=
filepath
.
Glob
(
fp
+
"-partial-*"
)
partFilePaths
,
err
:=
filepath
.
Glob
(
b
.
Name
+
"-partial-*"
)
if
err
!=
nil
{
return
err
}
var
total
,
completed
int64
var
parts
[]
BlobDownloadPart
for
_
,
partFilePath
:=
range
partFilePaths
{
var
part
BlobDownloadPart
partFile
,
err
:=
os
.
Open
(
partFilePath
)
part
,
err
:=
b
.
readPart
(
partFilePath
)
if
err
!=
nil
{
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
()
requestURL
=
requestURL
.
JoinPath
(
"v2"
,
opts
.
mp
.
GetNamespaceRepository
(),
"blobs"
,
opts
.
digest
)
if
len
(
parts
)
==
0
{
resp
,
err
:=
makeRequest
(
ctx
,
"HEAD"
,
requestURL
,
nil
,
nil
,
opts
.
regOpts
)
if
len
(
b
.
Parts
)
==
0
{
resp
,
err
:=
makeRequest
(
ctx
,
"HEAD"
,
requestURL
,
nil
,
nil
,
opts
)
if
err
!=
nil
{
return
err
}
defer
resp
.
Body
.
Close
()
total
,
_
=
strconv
.
ParseInt
(
resp
.
Header
.
Get
(
"Content-Length"
),
10
,
64
)
// reserve the file
f
.
Truncate
(
total
)
b
.
Total
,
_
=
strconv
.
ParseInt
(
resp
.
Header
.
Get
(
"Content-Length"
),
10
,
64
)
var
offset
int64
var
size
int64
=
64
*
1024
*
1024
for
offset
<
t
otal
{
if
offset
+
size
>
t
otal
{
size
=
t
otal
-
offset
for
offset
<
b
.
T
otal
{
if
offset
+
size
>
b
.
T
otal
{
size
=
b
.
T
otal
-
offset
}
parts
=
append
(
parts
,
BlobDownloadPart
{
Offset
:
offset
,
Size
:
size
,
})
partName
:=
b
.
Name
+
"-partial-"
+
strconv
.
Itoa
(
len
(
b
.
Parts
))
part
:=
blobDownloadPart
{
Offset
:
offset
,
Size
:
size
}
if
err
:=
b
.
writePart
(
partName
,
&
part
);
err
!=
nil
{
return
err
}
b
.
Parts
=
append
(
b
.
Parts
,
&
part
)
offset
+=
size
}
}
pw
:=
&
ProgressWriter
{
status
:
fmt
.
Sprintf
(
"downloading %s"
,
opts
.
digest
),
digest
:
opts
.
digest
,
total
:
total
,
completed
:
completed
,
fn
:
opts
.
fn
,
log
.
Printf
(
"downloading %s in %d part(s)"
,
b
.
Digest
[
7
:
19
],
len
(
b
.
Parts
))
return
nil
}
func
(
b
*
blobDownload
)
Run
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
opts
*
RegistryOptions
)
(
err
error
)
{
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
.
SetLimit
(
64
)
for
i
:=
range
p
arts
{
part
:=
p
arts
[
i
]
for
i
:=
range
b
.
P
arts
{
part
:=
b
.
P
arts
[
i
]
if
part
.
Completed
==
part
.
Size
{
continue
}
...
...
@@ -137,13 +120,17 @@ func downloadBlob(ctx context.Context, opts downloadOpts) error {
i
:=
i
g
.
Go
(
func
()
error
{
for
try
:=
0
;
try
<
maxRetries
;
try
++
{
if
err
:=
downloadBlobChunk
(
ctx
,
f
,
requestURL
,
parts
,
i
,
pw
,
opts
);
err
!=
nil
{
log
.
Printf
(
"%s part %d attempt %d failed: %v, retrying"
,
opts
.
digest
[
7
:
19
],
i
,
try
,
err
)
err
:=
b
.
downloadChunk
(
ctx
,
requestURL
,
i
,
opts
)
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
}
default
:
return
nil
}
}
return
errors
.
New
(
"max retries exceeded"
)
})
...
...
@@ -153,52 +140,67 @@ func downloadBlob(ctx context.Context, opts downloadOpts) error {
return
err
}
if
err
:=
f
.
Close
();
err
!=
nil
{
if
err
:=
b
.
Close
();
err
!=
nil
{
return
err
}
for
i
:=
range
p
arts
{
if
err
:=
os
.
Remove
(
f
.
Name
()
+
"-"
+
strconv
.
Itoa
(
i
));
err
!=
nil
{
for
i
:=
range
b
.
P
arts
{
if
err
:=
os
.
Remove
(
b
.
File
.
Name
()
+
"-"
+
strconv
.
Itoa
(
i
));
err
!=
nil
{
return
err
}
}
return
os
.
Rename
(
f
.
Name
(),
fp
)
}
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
{
if
err
:=
os
.
Rename
(
b
.
File
.
Name
(),
b
.
Name
);
err
!=
nil
{
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
w
:=
io
.
NewOffsetWriter
(
f
,
offset
)
w
:=
io
.
NewOffsetWriter
(
b
.
File
,
offset
)
headers
:=
make
(
http
.
Header
)
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
{
return
err
}
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
)
{
// rollback progress
bar
pw
.
c
ompleted
-=
n
// rollback progress
b
.
C
ompleted
.
Add
(
-
n
)
return
err
}
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
{
partFile
,
err
:=
os
.
OpenFile
(
n
ame
,
os
.
O_CREATE
|
os
.
O_RDWR
,
0644
)
func
(
b
*
blobDownload
)
writePart
(
partN
ame
string
,
part
*
b
lobDownloadPart
)
error
{
partFile
,
err
:=
os
.
OpenFile
(
partN
ame
,
os
.
O_CREATE
|
os
.
O_RDWR
|
os
.
O_TRUNC
,
0644
)
if
err
!=
nil
{
return
err
}
...
...
@@ -206,3 +208,85 @@ func flushPart(name string, part *BlobDownloadPart) error {
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