Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: status on errors #2036

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

fix: status on errors #2036

wants to merge 2 commits into from

Conversation

mxyng
Copy link
Contributor

@mxyng mxyng commented Jan 17, 2024

HTTP status on errors when stream:=false is always 500 Internal Server Error because the individual errors are not handled.

$ curl -v http://127.0.0.1:11434/api/pull -d '{"name":"does-not-exist","stream":false}'
*   Trying 127.0.0.1:11434...
* Connected to 127.0.0.1 (127.0.0.1) port 11434
> POST /api/pull HTTP/1.1
> Host: 127.0.0.1:11434
> User-Agent: curl/8.4.0
> Accept: */*
> Content-Length: 40
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 500 Internal Server Error
< Content-Type: application/json
< Date: Wed, 17 Jan 2024 23:36:45 GMT
< Content-Length: 52
<
* Connection #0 to host 127.0.0.1 left intact
{"error":"pull model manifest: file does not exist"}

The most common errors are:

  • pull, push: pulling or pushing a model that doesn't exist should return 404 Not Found
  • push: pushing a model into a place the user is authorized to should return 401 Unauthorized

Copy link
Contributor

@pdevine pdevine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up hitting this last night, so it's great to fix it. I think we should look at trying to simplify it though because it's not clear how the errors get propagated through the stream.

I also wish that we could make streaming/non-streaming throw the same response code. Maybe we could detect the 401/404 by making a call to HEAD the remote endpoint before writing the headers back for the SSEs?

@@ -965,7 +965,7 @@ func PushModel(ctx context.Context, name string, regOpts *RegistryOptions, fn fu
if err := uploadBlob(ctx, mp, layer, regOpts, fn); err != nil {
log.Printf("error uploading blob: %v", err)
if errors.Is(err, errUnauthorized) {
return fmt.Errorf("unable to push %s, make sure this namespace exists and you are authorized to push to it", ParseModelPath(name).GetNamespaceRepository())
return fmt.Errorf("%w: unable to push %s, make sure this namespace exists and you are authorized to push to it", err, ParseModelPath(name).GetNamespaceRepository())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does the final error look like here?

Copy link
Contributor Author

@mxyng mxyng Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the output is mostly the same except it prepends unauthorized:

{
    "error": "unauthorized: unable to push model, make sure this namespace exists and you are authorized to push to it"
}

@@ -953,6 +953,17 @@ func waitForStream(c *gin.Context, ch chan interface{}) {
c.JSON(http.StatusOK, r)
return
}
case error:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels more spaghettified than before w/ the gin.H case below. It's really unclear when error would be raised vs. when we would expect it to be set in gin.H.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally the channel should send 1) the response struct or 2) an error. it should never send gin.H. that's not the case right now as some handlers like generate and chat still send gin.H{"error": err.Error()}

@@ -977,6 +988,10 @@ func streamResponse(c *gin.Context, ch chan any) {
return false
}

if err, ok := val.(error); ok {
val = gin.H{"error": err.Error()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why unwrap the gin.H{} errors in the handlers than then rewrap it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's much harder to inspect an error if it's wrapped in gin.H since it stores the string representation error.Error(). since some errors are sometimes fmt.Errorf with variable content, it's impossible to determine what the real error is without string matching, which is tedious and error prone

inspect the real error is necessary for stream=false but since streamResponse and waitForStream consumes the same channel, this needs to receive real errors

HTTP status on errors when stream:=false is always 500 Internal Server
Error because the individual errors are not handled.

The most common errors are:

- pull, push: pulling or pushing a model that doesn't exist should
  return 404 Not Found
- push: pushing a model into a place the user is authorized to should
  return 401 Unauthorized
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants