Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion context.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ type frankenPHPContext struct {
// Whether the request is already closed by us
isDone bool

responseWriter http.ResponseWriter
responseWriter http.ResponseWriter
handlerParameters any
handlerReturn any

done chan any
startedAt time.Time
Expand Down
22 changes: 17 additions & 5 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,11 @@ PHP_FUNCTION(frankenphp_handle_request) {
zend_unset_timeout();
#endif

bool has_request = go_frankenphp_worker_handle_request_start(thread_index);
struct go_frankenphp_worker_handle_request_start_return result =
go_frankenphp_worker_handle_request_start(thread_index);
if (frankenphp_worker_request_startup() == FAILURE
/* Shutting down */
|| !has_request) {
|| !result.r0) {
RETURN_FALSE;
}

Expand All @@ -450,10 +451,15 @@ PHP_FUNCTION(frankenphp_handle_request) {

/* Call the PHP func passed to frankenphp_handle_request() */
zval retval = {0};
zval *callback_ret = NULL;

fci.size = sizeof fci;
fci.retval = &retval;
if (zend_call_function(&fci, &fcc) == SUCCESS) {
zval_ptr_dtor(&retval);
fci.params = result.r1;
fci.param_count = 1;

if (zend_call_function(&fci, &fcc) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
callback_ret = &retval;
}

/*
Expand All @@ -467,7 +473,13 @@ PHP_FUNCTION(frankenphp_handle_request) {
}

frankenphp_worker_request_shutdown();
go_frankenphp_finish_worker_request(thread_index);
go_frankenphp_finish_worker_request(thread_index, callback_ret);
if (result.r1 != NULL) {
zval_ptr_dtor(result.r1);
}
if (callback_ret != NULL) {
zval_ptr_dtor(&retval);
}

RETURN_TRUE;
}
Expand Down
24 changes: 16 additions & 8 deletions threadFramework.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"sync"
)

// WorkerExtension allows you to register an external worker where instead of calling frankenphp handlers on
// EXPERIMENTAL: WorkerExtension allows you to register an external worker where instead of calling frankenphp handlers on
// frankenphp_handle_request(), the ProvideRequest method is called. You are responsible for providing a standard
// http.Request that will be conferred to the underlying worker script.
//
Expand Down Expand Up @@ -36,21 +36,25 @@ type WorkerExtension interface {
ThreadActivatedNotification(threadId int)
ThreadDrainNotification(threadId int)
ThreadDeactivatedNotification(threadId int)
ProvideRequest() *WorkerRequest
ProvideRequest() *WorkerRequest[any, any]
}

type WorkerRequest struct {
// EXPERIMENTAL
type WorkerRequest[P any, R any] struct {
// The request for your worker script to handle
Request *http.Request
// Response is a response writer that provides the output of the provided request
// Response is a response writer that provides the output of the provided request, it must not be nil to access the request body
Response http.ResponseWriter
// Done is an optional channel that will be closed when the request processing is complete
Done chan struct{}
// CallbackParameters is an optional field that will be converted in PHP types and passed as parameter to the PHP callback
CallbackParameters P
// AfterFunc is an optional function that will be called after the request is processed with the original value, the return of the PHP callback, converted in Go types, is passed as parameter
AfterFunc func(callbackReturn R)
}

var externalWorkers = make(map[string]WorkerExtension)
var externalWorkerMutex sync.Mutex

// EXPERIMENTAL
func RegisterExternalWorker(worker WorkerExtension) {
externalWorkerMutex.Lock()
defer externalWorkerMutex.Unlock()
Expand All @@ -77,15 +81,19 @@ func startExternalWorkerPipe(w *worker, externalWorker WorkerExtension, thread *

if fc, ok := fromContext(fr.Context()); ok {
fc.responseWriter = rq.Response
fc.handlerParameters = rq.CallbackParameters

// Queue the request and wait for completion if Done channel was provided
logger.LogAttrs(context.Background(), slog.LevelInfo, "queue the external worker request", slog.String("worker", w.name), slog.Int("thread", thread.threadIndex))

w.requestChan <- fc
if rq.Done != nil {
if rq.AfterFunc != nil {
go func() {
<-fc.done
close(rq.Done)

if rq.AfterFunc != nil {
rq.AfterFunc(fc.handlerReturn)
}
}()
}
}
Expand Down
23 changes: 17 additions & 6 deletions threadworker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log/slog"
"path/filepath"
"time"
"unsafe"
)

// representation of a thread assigned to a worker script
Expand Down Expand Up @@ -159,7 +160,7 @@ func tearDownWorkerScript(handler *workerThread, exitStatus int) {
}

// waitForWorkerRequest is called during frankenphp_handle_request in the php worker script.
func (handler *workerThread) waitForWorkerRequest() bool {
func (handler *workerThread) waitForWorkerRequest() (bool, any) {
// unpin any memory left over from previous requests
handler.thread.Unpin()

Expand Down Expand Up @@ -195,7 +196,7 @@ func (handler *workerThread) waitForWorkerRequest() bool {
C.frankenphp_reset_opcache()
}

return false
return false, nil
case fc = <-handler.thread.requestChan:
case fc = <-handler.worker.requestChan:
}
Expand All @@ -205,23 +206,33 @@ func (handler *workerThread) waitForWorkerRequest() bool {

logger.LogAttrs(ctx, slog.LevelDebug, "request handling started", slog.String("worker", handler.worker.name), slog.Int("thread", handler.thread.threadIndex), slog.String("url", fc.request.RequestURI))

return true
return true, fc.handlerParameters
}

// go_frankenphp_worker_handle_request_start is called at the start of every php request served.
//
//export go_frankenphp_worker_handle_request_start
func go_frankenphp_worker_handle_request_start(threadIndex C.uintptr_t) C.bool {
func go_frankenphp_worker_handle_request_start(threadIndex C.uintptr_t) (C.bool, unsafe.Pointer) {
handler := phpThreads[threadIndex].handler.(*workerThread)
return C.bool(handler.waitForWorkerRequest())
hasRequest, parameters := handler.waitForWorkerRequest()

if parameters != nil {
p := PHPValue(parameters)
handler.thread.Pin(p)

return C.bool(hasRequest), p
}

return C.bool(hasRequest), nil
}

// go_frankenphp_finish_worker_request is called at the end of every php request served.
//
//export go_frankenphp_finish_worker_request
func go_frankenphp_finish_worker_request(threadIndex C.uintptr_t) {
func go_frankenphp_finish_worker_request(threadIndex C.uintptr_t, retval *C.zval) {
thread := phpThreads[threadIndex]
fc := thread.getRequestContext()
fc.handlerReturn = GoValue(unsafe.Pointer(retval))

fc.closeContext()
thread.handler.(*workerThread).workerContext = nil
Expand Down