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

Move app initialization code to a weaver.Main method. #359

Merged
merged 3 commits into from
May 30, 2023

Conversation

ghemawat
Copy link
Collaborator

Previously, application startup code was wrapped up in a function passed to weaver.Run. However this made it hard to test startup code. We now have a Main method in weaver.Main and place the startup code in the implementation of this method.

Refactored startup the internal API used by weavertest to separate out starting the application from running its startup code.

Previously, application startup code was wrapped up in a function
passed to weaver.Run. However this made it hard to test startup
code. We now have a `Main` method in `weaver.Main` and place the
startup code in the implementation of this method.

Refactored startup the internal API used by weavertest to separate out
starting the application from running its startup code.
@ghemawat ghemawat requested a review from mwhittaker May 26, 2023 22:35
Copy link
Member

@mwhittaker mwhittaker left a comment

Choose a reason for hiding this comment

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

Awesome change Sanjay! I'm loving the new API :)

component.go Outdated
// This component is instantiated and its Main() method called by
// `weaver.Run`.
type Main interface {
// TODO(reviewer): Should we make this method optional?
Copy link
Member

Choose a reason for hiding this comment

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

If someone opts to not implement the Main method, what would be the behavior of weaver.Run? I guess we would still run the main component's Init method and then block forever?

I like making weaver.Ref[weaver.Main] not compile somehow.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, my intention was that it would block.

I will try to make weaver.Ref[weaver.Main] not compile. And perhaps make the code generator refuse to make RPCs for Main?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

PTAL

@@ -40,13 +42,14 @@ const matchNothingRE = "a^" // Regular expression that never matches
// component level configs. config is allowed to be empty.
//
// Future extension: allow options so the user can control collocation/replication/etc.
func initMultiProcess(ctx context.Context, name string, isBench bool, runner Runner, logWriter func(*protos.LogEntry)) (context.Context, func() error, error) {
func initMultiProcess(ctx context.Context, t testing.TB, isBench bool, runner Runner, logWriter func(*protos.LogEntry)) (context.Context, func() error, error) {
Copy link
Member

Choose a reason for hiding this comment

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

If I'm understanding correctly, if a main component exists, every weavertest will automatically run its Main method. I think previously you suggested the idea of not running Main automatically and instead having users manually call it like this:

runner.Run(t, func(m weaver.Main) {
    m.Main(context.Background())
})

Do you still think that's a good idea?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That's a good point. I think that I am leaning towards sticking with letting weaver/weavertest call Main.Main (as opposed to the user explicitly calling it) to keep things similar between non-test and test code. Otherwise, the user has to remember that in scenario Main.Main is called automatically and in another they are responsible.

Note also that Main.Main will only run in weavelets that are hosting weaver.Main. So this could just be considered part of the issue of how test code controls replication.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good to me!

Copy link
Member

Choose a reason for hiding this comment

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

I was writing some unit tests and ran into two things that are a bit annoying with the new API (assuming there is more than one test). They are both related to Main being run more than once.

  1. You can't call http.HandleFunc (or any other function that registers an endpoint with the default mux). When the second test runs, it calls Main again, and http.HandleFunc panics.
  2. If your server listens on a fixed port, like localhost:9000, the second test will fail with a bind: address already in use if your Main doesn't respect the context it receives.

I'm not sure how to fix these issues, but wanted to document them somewhere.

component.go Outdated
// This component is instantiated and its Main() method called by
// `weaver.Run`.
type Main interface {
// TODO(reviewer): Should we make this method optional?
Copy link
Contributor

Choose a reason for hiding this comment

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

Making it optional would involve maintaining Main as an empty interface and using reflection to discover if the Main impl implements this method?

That seems less clear to me for the user. I like your alternatives below, however.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. The code is pretty simple, but the question is whether or not it is more complicated for the user. So I will try to just disable Ref[] and RPCs.

examples/factors/main.go Show resolved Hide resolved
Copy link
Member

@mwhittaker mwhittaker left a comment

Choose a reason for hiding this comment

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

Looks great!

@@ -40,13 +42,14 @@ const matchNothingRE = "a^" // Regular expression that never matches
// component level configs. config is allowed to be empty.
//
// Future extension: allow options so the user can control collocation/replication/etc.
func initMultiProcess(ctx context.Context, name string, isBench bool, runner Runner, logWriter func(*protos.LogEntry)) (context.Context, func() error, error) {
func initMultiProcess(ctx context.Context, t testing.TB, isBench bool, runner Runner, logWriter func(*protos.LogEntry)) (context.Context, func() error, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Sounds good to me!

@ghemawat ghemawat merged commit 570d1ee into ServiceWeaver:main May 30, 2023
7 checks passed
@ghemawat ghemawat deleted the mainmethod branch May 30, 2023 19:28
mwhittaker added a commit that referenced this pull request Jun 20, 2023
> Overview

In #359, we changed weavertest to automatically run the main component's
Main method for every test. This was done to allow a developer to test
the server they started in the Main method.

However, we realized that this approach of automatically running the
Main method has some drawbacks. Because the Main method is run for every
test, if you do anything in the Main method that cannot be run more than
once, the tests will fail. For example, if you register a handler with
the default serve mux using `http.HandleFunc`, all tests after the first
will panic.

After some offline discussion, we decided not to run the Main method
automatically. Additionally, we'll allow people to get pointers to
component implementations in a test. Then, developers can test the guts
of their Main method using existing things like the httptest package.
This PR changes weavertest to not run the Main method automatically. In
a future CL, I'll allow developers to get pointers to component
implementations.

> Details

- I removed weavertest.GetListenerAddress. It will no longer be needed.
- I removed the apps variable from the weavertest package. It was only
  needed to implement GetListenerAddress.
- I removed testMainInterface from the weavertest package. I think this
  was some stale code lingering around from an earlier implementation of
  weavertest.
mwhittaker added a commit that referenced this pull request Jun 20, 2023
This PR reverts #359. In particular, it removes the main component's
Main method and changes `weaver.Run` to take a lambda.

We made this change for the following reason. The Main method is run by
`weaver.Run`, but not by `weavertest.Runner.Test`. We felt this was
confusing. With the lambda API, it is much clearer what is run. This
change also unifies the `weaver.Run` and `weavertest.Runner.Test` API.
Both take a lambda with component arguments.

Right now, the signature of `weaver.Run` is a bit gnarly. I think I can
simplify it a bit in a future PR to remove PointerToMain at least.
mwhittaker added a commit that referenced this pull request Jun 21, 2023
> Overview

In #359, we changed weavertest to automatically run the main component's
Main method for every test. This was done to allow a developer to test
the server they started in the Main method.

However, we realized that this approach of automatically running the
Main method has some drawbacks. Because the Main method is run for every
test, if you do anything in the Main method that cannot be run more than
once, the tests will fail. For example, if you register a handler with
the default serve mux using `http.HandleFunc`, all tests after the first
will panic.

After some offline discussion, we decided not to run the Main method
automatically. Additionally, we'll allow people to get pointers to
component implementations in a test. Then, developers can test the guts
of their Main method using existing things like the httptest package.
This PR changes weavertest to not run the Main method automatically. In
a future CL, I'll allow developers to get pointers to component
implementations.

> Details

- I removed weavertest.GetListenerAddress. It will no longer be needed.
- I removed the apps variable from the weavertest package. It was only
  needed to implement GetListenerAddress.
- I removed testMainInterface from the weavertest package. I think this
  was some stale code lingering around from an earlier implementation of
  weavertest.
mwhittaker added a commit that referenced this pull request Jun 21, 2023
> Overview

In #359, we changed weavertest to automatically run the main component's
Main method for every test. This was done to allow a developer to test
the server they started in the Main method.

However, we realized that this approach of automatically running the
Main method has some drawbacks. Because the Main method is run for every
test, if you do anything in the Main method that cannot be run more than
once, the tests will fail. For example, if you register a handler with
the default serve mux using `http.HandleFunc`, all tests after the first
will panic.

After some offline discussion, we decided not to run the Main method
automatically. Additionally, we'll allow people to get pointers to
component implementations in a test. Then, developers can test the guts
of their Main method using existing things like the httptest package.
This PR changes weavertest to not run the Main method automatically. In
a future CL, I'll allow developers to get pointers to component
implementations.

> Details

- I removed weavertest.GetListenerAddress. It will no longer be needed.
- I removed the apps variable from the weavertest package. It was only
  needed to implement GetListenerAddress.
- I removed testMainInterface from the weavertest package. I think this
  was some stale code lingering around from an earlier implementation of
  weavertest.
mwhittaker added a commit that referenced this pull request Jun 21, 2023
This PR reverts #359. In particular, it removes the main component's
Main method and changes `weaver.Run` to take a lambda.

We made this change for the following reason. The Main method is run by
`weaver.Run`, but not by `weavertest.Runner.Test`. We felt this was
confusing. With the lambda API, it is much clearer what is run. This
change also unifies the `weaver.Run` and `weavertest.Runner.Test` API.
Both take a lambda with component arguments.

Right now, the signature of `weaver.Run` is a bit gnarly. I think I can
simplify it a bit in a future PR to remove PointerToMain at least.
mwhittaker added a commit that referenced this pull request Jun 21, 2023
This PR reverts #359. In particular, it removes the main component's
Main method and changes `weaver.Run` to take a lambda.

We made this change for the following reason. The Main method is run by
`weaver.Run`, but not by `weavertest.Runner.Test`. We felt this was
confusing. With the lambda API, it is much clearer what is run. This
change also unifies the `weaver.Run` and `weavertest.Runner.Test` API.
Both take a lambda with component arguments.

Right now, the signature of `weaver.Run` is a bit gnarly. I think I can
simplify it a bit in a future PR to remove PointerToMain at least.
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

3 participants