go-swagger / go-swagger

Swagger 2.0 implementation for go
https://goswagger.io
Apache License 2.0
9.54k stars 1.25k forks source link

malloc deadlock #1454

Closed tortuoise closed 6 years ago

tortuoise commented 6 years ago

Problem statement

Fatal error: malloc deadlock using generated server and client

Swagger specification

swagger: '2.0'
info:
  version: 0.1.0
  title: Simple To Do List API
securityDefinitions:
  key:
    type: apiKey
    in: header
    name: x-todolist-token
security:
  - key: [asdf]
consumes:
  - application/io.swagger.examples.todo-list.v1+json
produces:
  - application/io.swagger.examples.todo-list.v1+json
schemes:
  - http
  - https
x-schemes:
  - unix
paths:
  /:
    get:
      tags: ["todos"]
      operationId: find
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: formData
          type: integer
          format: int32
          required: true
          allowEmptyValue: true
        - name: "X-Rate-Limit"
          in: header
          type: integer
          format: int32
          required: true
        - name: tags
          in: formData
          type: array
          collectionFormat: multi
          allowEmptyValue: true
          items:
            type: integer
            format: int32
          required: true
      responses:
        '200':
          description: OK
          schema:
            type: array
            items:
              $ref: "#/definitions/item"
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
    post:
      tags: ["todos"]
      operationId: addOne
      parameters:
        - name: body
          in: body
          schema:
            $ref: "#/definitions/item"
      responses:
        '201':
          description: Created
          schema:
            $ref: "#/definitions/item"
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
  /{id}:
    parameters:
      - type: integer
        format: int64
        name: id
        in: path
        required: true
    put:
      tags: ["todos"]
      operationId: updateOne
      parameters:
        - name: body
          in: body
          schema:
            $ref: "#/definitions/item"
      responses:
        '200':
          description: OK
          schema:
            $ref: "#/definitions/item"
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
    delete:
      tags: ["todos"]
      operationId: destroyOne
      responses:
        '204':
          description: Deleted
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean
  error:
    type: object
    required:
      - message
    properties:
      code:
        type: integer
        format: int64
      message:
        type: string
  principal:
    type: string

Steps to reproduce

Generate todos server and client and then create new package main with this:

    package main
    import (
      "fmt"
      "github.com/user/todos1/client"
      "github.com/user/todos1/models"
      "github.com/user/todos1/client/todos"
      "github.com/go-openapi/runtime"
      "github.com/go-openapi/strfmt"
    )

    func main() {
      t1 := client.NewHTTPClient(strfmt.Default)

      hand := runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error {
        return r.SetHeaderParam("x-todolist-token", "asdf")
      })

      x := "Do this and that"
      a1 := todos.NewAddOneParams()
      a1 = a1.WithBody(&models.Item{Description: &x})
      added, err := t1.Todos.AddOne(a1, hand)  
      if err != nil {
        fmt.Println("Error: ", err)
      }

      if added != nil {
        fmt.Printf("%#v\n",added.Payload)
        fmt.Println(added.Error())
      } else {
        fmt.Println("Added nil")
      }
    }

Run server and run client snippet above. Error results.

Just passing nil to AddOne which calls NewAddOneParams() without a body works without error.

Stack trace:

panic: fatal error: malloc deadlock
  panic during panic

  runtime stack:
  runtime.startpanic_m()
     /home/user/dev/go/src/runtime/panic.go:693 +0x179
  runtime.systemstack(0x0)
     /home/user/dev/go/src/runtime/asm_amd64.s:409 +0x79
  runtime.mstart()
     /home/user/dev/go/src/runtime/proc.go:1170

  goroutine 1 [running]:
  runtime.systemstack_switch()
     /home/user/dev/go/src/runtime/asm_amd64.s:363 fp=0xc420477748 sp=0xc420477740 pc=0x454130
  runtime.startpanic()
     /home/user/dev/go/src/runtime/panic.go:592 +0x1e fp=0xc420477760 sp=0xc420477748 pc=0x42af3e
  runtime.throw(0x83a192, 0xf)
     /home/user/dev/go/src/runtime/panic.go:618 +0x74 fp=0xc420477780 sp=0xc420477760 pc=0x42b064
  runtime.mallocgc(0x2010, 0x0, 0x7cc001, 0x7645c3)
     /home/user/dev/go/src/runtime/malloc.go:621 +0x99d fp=0xc420477820 sp=0xc420477780 pc=0x411f4d
  runtime.itabAdd(0x7f9a235ac968)
     /home/user/dev/go/src/runtime/iface.go:122 +0x83 fp=0xc420477870 sp=0xc420477820 pc=0x40ee63
  runtime.getitab(0x7cc0a0, 0x79fda0, 0xc42018f601, 0xc42018f640)
     /home/user/dev/go/src/runtime/iface.go:70 +0x404 fp=0xc4204778e8 sp=0xc420477870 pc=0x40ece4
  runtime.assertE2I2(0x7cc0a0, 0x79fda0, 0xc4201e7790, 0x7, 0xc400000007, 0xc420477978)
     /home/user/dev/go/src/runtime/iface.go:592 +0x43 fp=0xc420477918 sp=0xc4204778e8 pc=0x4101a3
  runtime.printany(0x79fda0, 0xc4201e7790)
     /home/user/dev/go/src/runtime/error.go:77 +0x69 fp=0xc4204779d8 sp=0xc420477918 pc=0x4071e9
  runtime.printpanics(0xc420477a60)
     /home/user/dev/go/src/runtime/panic.go:420 +0x71 fp=0xc4204779f8 sp=0xc4204779d8 pc=0x42a781
  panic(0x7c5240, 0xa83eb0)
     /home/user/dev/go/src/runtime/panic.go:553 +0x3b4 fp=0xc420477a98 sp=0xc4204779f8 pc=0x42abc4
  runtime.panicmem()
     /home/user/dev/go/src/runtime/panic.go:63 +0x5e fp=0xc420477ab8 sp=0xc420477a98 pc=0x429aae
  runtime.sigpanic()
     /home/user/dev/go/src/runtime/signal_unix.go:388 +0x17a fp=0xc420477b08 sp=0xc420477ab8 pc=0x43f91a
  github.com/user/todos1/vendor/github.com/go-openapi/runtime/client.(*request).buildHTTP(0xc42050e300, 0x849d18, 0x31, 0x83135e, 0x1, 0xc420526030, 0x0, 0x0, 0x8835c0, 0x852988, ...) 
     /home/user/dev/mygo/src/github.com/user/todos1/vendor/github.com/go-openapi/runtime/client/request.go:229 +0xc56 fp=0xc420477cc0 sp=0xc420477b08 pc=0x756dd6
  github.com/user/todos1/vendor/github.com/go-openapi/runtime/client.(*Runtime).Submit(0xc4205000e0, 0xc4205080c0, 0x0, 0x0, 0x0, 0x0) 
     /home/user/dev/mygo/src/github.com/user/todos1/vendor/github.com/go-openapi/runtime/client/runtime.go:250 +0x327 fp=0xc420477e68 sp=0xc420477cc0 pc=0x758e97
  github.com/user/todos1/client/todos.(*Client).AddOne(0xc420412e20, 0xc420526060, 0x8835c0, 0x852988, 0x83a165, 0xf, 0x83135e)
     /home/user/dev/mygo/src/github.com/user/todos1/client/todos/todos_client.go:36 +0x30f fp=0xc420477eb8 sp=0xc420477e68 pc=0x75d68f
  main.main()
     /home/user/dev/mygo/src/github.com/user/todos1/client/cmd/todos1-client/main.go:25 +0x10f fp=0xc420477f88 sp=0xc420477eb8 pc=0x75feff
  runtime.main()
     /home/user/dev/go/src/runtime/proc.go:198 +0x212 fp=0xc420477fe0 sp=0xc420477f88 pc=0x42c8e2
  runtime.goexit()
     /home/user/dev/go/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc420477fe8 sp=0xc420477fe0 pc=0x456c81

Environment

swagger version: x.x.x
go version: 1.10 OS: Ubuntu 16.04..3 LTS

tortuoise commented 6 years ago

Will be good to have somebody replicate this problem or point out any errors in my example setup.

casualjim commented 6 years ago

yeah I can reproduce the error, it's becuase there is no producer registered. The following version of your code works properly:

package main

import (
    "fmt"

    "playground/client"
    "playground/client/todos"
    "playground/models"

    "github.com/go-openapi/runtime"
    httptransport "github.com/go-openapi/runtime/client"
    "github.com/go-openapi/strfmt"
)

func main() {
    cfg := client.DefaultTransportConfig()
    cfg.Host = "localhost:40083"
    cfg.Schemes = []string{"http"}

    t1 := client.NewHTTPClient(strfmt.Default)
    tr := httptransport.New(cfg.Host, cfg.BasePath, cfg.Schemes)
    tr.Consumers["application/io.swagger.examples.todo-list.v1+json"] = runtime.JSONConsumer()
    tr.Producers["application/io.swagger.examples.todo-list.v1+json"] = runtime.JSONProducer()
    t1.SetTransport(tr)

    hand := runtime.ClientAuthInfoWriterFunc(func(r runtime.ClientRequest, _ strfmt.Registry) error {
        return r.SetHeaderParam("x-todolist-token", "asdf")
    })

    x := "Do this and that"
    a1 := todos.NewAddOneParams()
    a1 = a1.WithBody(&models.Item{Description: &x})
    added, err := t1.Todos.AddOne(a1, hand)
    if err != nil {
        fmt.Println("Error: ", err)
    }

    if added != nil {
        fmt.Printf("%#v\n", added.Payload)
        fmt.Println(added.Error())
    } else {
        fmt.Println("Added nil")
    }
}
tortuoise commented 6 years ago

Thanks very much! I was close to giving up on client gen.

tortuoise commented 6 years ago

Will it be useful to add a map access check to go-openapi/runtime/client/runtime.go before the call to buildHTTP?

if _, ok := r.Producers[cmt]; !ok { return nil, fmt.Errorf("%v", "Producer not registered") }

casualjim commented 6 years ago

sounds like a good idea, are you offering a PR?

tortuoise commented 6 years ago

Done.