-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Refactor Fiber internal implementation #7085
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
Conversation
Also align the macro names and generally macro definitions are always at the top of the file.
Without giving this a thorough examination because I don't have time today, there needs to be a way to know if you are within Can you explain the motivation of treating While the |
@@ -7,7 +7,7 @@ $value = Fiber::suspend(1); | |||
|
|||
?> | |||
--EXPECTF-- | |||
Fatal error: Uncaught FiberError: Cannot suspend outside of a fiber in %ssuspend-outside-fiber.php:%d |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in this particular instance the old description is more obvious than "nowhere to go".
Quick note: The observer test output is a bit dubious because the inner fiber is destroyed while destroying the outer fiber, so the switch back to the outer fiber prints the destroying/destroyed pair twice. I do like having separate status and flag fields so the observer could differentiate between these states as you did, so +1 to that portion. |
I think a definite advantage of treating main like a fiber is just defensive programming - there's no need to NULL check, you always jump from a fiber to another fiber, not from a fiber to null. I prefer this approach as well. It just probably should expose something to distinguish there. |
@bwoebi A disadvantage is that |
What many people have not recognized is that the reason why the main fiber can not suspend is not it is main fiber, but because it has not previous one, that's why I changed the error message to "Fiber has nowhere to go".
I have explained a part of the reasons in another thread, as @bwoebi said. Let main fiber be NULL will causes disaster (I am talking from my experience), and we have to check it everywhere. But this is not the biggest advantage of it. Of course that if you still want to treat main differently, provide |
For example? It's definitely no for me. Imagine if other extensions implement their own fiber and his fiber has a different memory struct with |
@@ -622,6 +622,11 @@ ZEND_METHOD(Fiber, this) | |||
{ | |||
ZEND_PARSE_PARAMETERS_NONE(); | |||
|
|||
/* Follow RFC for now */ | |||
if (EG(current_fiber) == EG(main_fiber)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though it seems ridiculous to me, but it's the fast way to solve the BC break here...
Having written a lot of fiber code, "have to check it everywhere" is not the case using the proper abstraction if you want to treat main as though it were a fiber.
So you do not think a C-stack switching API necessary but you plan to add it back in another PR? I'm confused. Fine if it isn't part of the Zend API, but IMO scattering the content of those functions into others is not helping readability. This PR is changing the existing asymmetric fiber model to a symmetric model where fibers are not modeled as a stack, but rather independent "threads" where each must determine the next fiber to resume (which is often the prior fiber) rather than always suspending to the prior context. I think if we went this direction, we would want the user API to reflect this model and remove I really like a couple of the ideas you have here, particularly the way you separated fiber status from flags, returned bool from |
I have a two questions about fibers:
|
Sorry for my poor English... I mean we have to check it everywhere if we have not main fiber abstraction.
I mean we can run the pure C function with zend_fiber directly (and we don't need to switch ZendVM stack for pure C fiber),
I do not think so, I just simplified the code logic.
Thank you for your approval. But can we confirm all of these here, it could reduce my workload, we still have a lot to do... unless you are totally opposed to other changes. |
Hi, @mvorisek
I think this has included in the RFC... I think some of us still have limited knowledge of it although it has discussed and landed.
No. Technologies like Swoole are more like nodejs in golang style. It cannot solve the performance problem of CPU-intensive computing, but it can solve the problem of intensive IO. |
We seem to be still misunderstanding each other, sorry. I was alluding to a scheduler running in a fiber that switches between My future intention is to run the scheduler (event-loop) within
Yes, an extension can use
The internal API is now symmetric, that's why
There are parts to which I'm opposed. I'm unsure about putting a single fiber object onto the VM stack only for the main fiber to avoid a single alloc. IMO, the complexity does not justify any perceived performance gain. I think using The PR is noisy due to superfluous changes, such as rearranging functions and renaming parameters because you prefer "data" over "value." This makes some changes more difficult to notice (such as I just noticed you're now using I'd prefer these changes to come as separate PRs so we can better understand all the changes that are being made. If you don't have the time, I can break some of this down and tag you as a co-author. |
The design I advocate is the content that has been verified in Swoole. About
Performance is not important, because they are always on some magnitude of time.
Diff does look terrible for me too... we have ensured that all tests pass and are consistent with the original behavior, I think it may be better if we re-read it instead of looking at the diff. I believe the new code has clearer logic, otherwise refactoring is meaningless...
It's OK for me. I just want to speed up. I always work on open-source projects :) |
I still do not think so. |
The protected page only triggers an error if memory is accessed. If a large chunk of memory were allocated on the stack it could exceed the size of a single page. I don't see this as a problem in php-src where 1 page should be more than adequate, though I can see the potential for users of a particular extension to use multiple guard pages.
Internally it does, this PR uses a symmetric model as outlined in the link you provided. As mentioned in that link, asymmetric coroutines are the main abstraction provided by Boost.Coroutine2. I feel there is some wisdom in this and hesitate to deviate from that model. Swoole also appears to use asymmetric cotroutines both in its internal and user APIs.
I should have time tomorrow and this weekend to start splitting this up into smaller PRs. I'll definitely tag you as co-author on any commits. I'm happy to collaborate on future changes. Language has been a barrier in the past so I'm happy to do what I can to help. |
I think what we need to guard against is recursive calls rather than big memory on the stack, and I did not see the code for multi-page memory protection in other coroutine projects. Maybe you can specify the reference of this idea, it is new to me. About symmetric and asymmetric, maybe you are entangled in these concepts, I only focused on its actual behaviour, I think these concepts are defined based on the behaviour of specified technological. I don't quite understand what you are worried about . And Swoole has never stated what kind of model its' coroutine is, it do not care.
You may have misunderstood what I meant, writing code is fine for me, I work on open source projects all the time. we still have a lot to do on fiber did not mean that I have no time, I just want to speed up the process of these changes, especially after you have already understood all of these changes ^^ |
I hope you can directly comment on my PR here instead of the chat room and I can't speak there. |
And It's interesting. Although I made it clear that I am willing to complete these tasks, you still committed part of the code I wrote, and you are so eager. I do not care whether I am co-author, it would be great if you can copy every thought of mine and commit them, then we can make zend_fiber play with Swoole. But you probably wouldn't do it. So, let zend_fiber and amphp have fun. |
The asynchronous programming of php should be more like go's coroutine, single-threaded coroutine is the best, obviously fiber is still on the way to pit, maybe we should learn from the experience of swoole and swow |
@twose I apologize for upsetting you, that was not my intention. I know you're capable of breaking up this PR, but I was unsure if you intended to do so, as I and others asked you to do that here and in other discussions. I too want to keep things moving forward, so I committed a portion of the changes as they weren't controversial and had clear advantages. There are other parts of this PR I would like to commit, but not all of them, which is why I wanted to break things into multiple PRs. I hope you'll still open more PRs focused on smaller changes at a time. I do want to work with you in the future, but please understand that we may have disagreements. The language you used feels like an attack on my motivations, and isn't the type of conversation that makes me want to continue trying to put the work in to address your concerns. |
|
|
No BC breaks at the user level.
I tried to divide them into multiple PRs but I failed, changes depend on each other to a certain extent.
Introduced main fiber. It's one of the most important parts of this PR. Any C stack can be abstracted as fiber, the main fiber in fibers is like the main thread in threads.
Put the fiber object and its context on its own C stack. This is the same trick as the zend_vm_stack does. It also works on the main fiber, we can put the main fiber on the main PHP VM stack without emalloc.
Add
zend_fiber_jump
and merge code ofresume
/suspend
, this is the biggest problem in the previous implementation. The same code appears repeatedly in various places before. I believe the logic of fiber switching is clearer now.Change the order of executor_globals members, so that we can switch context through a memcpy instead of multiple assignments, this simplifies the code, and for better performance.
boost context provided the transfer structure to let users can transfer ptr data between fibers, so we can transfer zval ptr to fiber and use ZVAL_COPY on the peer side without an extra field to store zval. But we still need a zval to store the return value of fiber because we provided
getReturn
Remove the
exception
field fromzend_fiber
, we can use one thread-safe global var instead.About the ASan part (af29059 ), the code makes me confused, I believe the correct/clearer example is https://github.com/boostorg/context/blob/master/include/boost/context/continuation_ucontext.hpp
Rename
value
todata
, as boost called ittransfer.data
I believe it's more clear to named itdata
thanvalue
Rename
caller
tofrom
, as boost called ittransfer.from
(also in boost.fiber), which means where the switch came from.Nested fibers are maintained by linked list structure, we usually use
prev
to represent the previous one, I named itprevious
here.We only need four statuses,
INIT
/SUSPENDED
/RUNNING
/DEAD
(same with Lua coroutine), and it's better to make it be an enum, any other internal status information is expressed infiber->flags
Remove zend_fiber_context APIs, they are meaningless
Also, since we do not want to break the RFC, there are many optimizations or enhancements that can not be implemented for now. It is still the beginning.