httpexpect icon indicating copy to clipboard operation
httpexpect copied to clipboard

Make structs thread-safe

Open gavv opened this issue 2 years ago • 10 comments

Background: https://github.com/gavv/httpexpect/discussions/229

I've pushed a few changes that make the chain struct thread-safe:

  • 0547acd58636b8dc199f43fd7206d9bb629e7852
  • a96122195174a5b9df2804e41c8abe92a90fc52b
  • 90675e11729f5dfa5121bbf41a1e071dbf9e52e7

This automatically makes many structs thread-safe too: Expect, Value, Array, Object, etc. It's true for matcher structs which contains only a chain + immutable data.

The next step is to manually implement thread-safety for the following structs:

  • [x] Environment
  • [x] Request
  • [ ] Websocket

These three structs has mutable state and should be protected manually. For each of them, we should add sync.RWMutex field and protect all operations. We can remove noCopy fields from these structs.

Request struct needs special care: it contains transforms and matchers callbacks that are invoked during Expect() call. We should NOT call them under a lock because otherwise it would be easy to write code with deadlocks (if you use the same request from this callbacks).

Websocket also needs some special handling. It should allow to read and write messages concurrently and to call disconnect concurrently (similarly as gorilla websocket which it uses under the hood).

After finishing this, we should update docs:

  • [ ] add thread-safety section to package documentation, mention limitation placed by httpexpect, testify, and standard testing
  • [ ] add note to README

In addition, we should improve our testing suite:

  • [ ] add make race target which runs all tests under race detector
  • [ ] add corresponding step to CI
  • [ ] create issue for implementing stress test

gavv avatar Jan 24 '23 14:01 gavv

@gavv I'd like to take this up. Sounds interesting!!

Rohith-Raju avatar Jan 25 '23 03:01 Rohith-Raju

Great!

gavv avatar Jan 25 '23 06:01 gavv

I suggest to split work into five PRs:

  • [x] modify chain to postpone failure reporting until leave() is called
  • [x] make Environment thread-safe (easy part)
  • [ ] make Request thread-safe (harder)
  • [ ] make Websocket thread-safe (harder)
  • [ ] add make race to makefile and CI
  • [ ] add stress test (not very hard but big and time consuming)

UPDATED

gavv avatar Jan 26 '23 11:01 gavv

Added this paragraph to the issue:

Websocket also needs some special handling. It should allow to read and write messages concurrently and to call disconnect concurrently (similarly as gorilla websocket which it uses under the hood).

gavv avatar Jan 26 '23 11:01 gavv

@Rohith-Raju

I was reviewing #254 and realized that there is a problem with our approach.

We acquire a lock, then perform a check, and then report failure, under the lock. A potential problem is that reporting failure means invoking AssertionHandler, Reporter, Logger, Formatter, which may be implemented by user. In other words, we invoke user code under a lock.

If this user code will try to access our object, we'll get deadlock. For example, if AssertionHandler tries to get a value from env, env method will try to acquire a lock and will hang.

I have an idea of quite generic approach to avoid this. We can introduce three rules:

  • every operation starts with entering chain; this rule is already followed

  • if operation needs a lock, it's acquired after entering chain and released before leaving chain

  • when fail is called, it records failure but doesn't report it; it will be reported only when leave is called

This way, failures are guaranteed to be reported outside of the lock (because leave is called after releasing lock). Which means that associated user code is also called outside of the lock.

Two simple changes are needed:

  • in your pr, just reorder enter/leave block and lock/unlock block in every method

  • modify chain to postpone failure reporting from fail() to leave(); if possible please do it in a separate pr and we'll merge it first

gavv avatar Jan 29 '23 18:01 gavv

https://github.com/gavv/httpexpect/pull/293#issuecomment-1441477241

gavv avatar Feb 23 '23 09:02 gavv

6c92b922b452f5ac0e64d6eda4ff0201b0ac97bf

gavv avatar Feb 23 '23 12:02 gavv

Is this still open?

sujeetsawala avatar Apr 28 '23 06:04 sujeetsawala

@sujeetsawala yes. I plan to finish it by myself, except stress test. If you would like to help with stress test, you're welcome! It can be done independently of other stuff.

gavv avatar Apr 28 '23 06:04 gavv

@gavv I have the following questions:-

  1. A multi-process or a single-process?
  2. Should it be generalised so that functions to be stress tested can be injected into or a particular for this.
  3. I was looking at the stress go library (https://pkg.go.dev/golang.org/x/tools/cmd/stress) which provides the above functionalities out of the box.

Can you explain the objectives of our stress test?

sujeetsawala avatar Apr 28 '23 10:04 sujeetsawala