Skip to content

Commit 661c684

Browse files
committed
fix(windows): share sockets across PHP threads --filter=[win-unit]
1 parent ecc6d9d commit 661c684

13 files changed

Lines changed: 192 additions & 105 deletions

File tree

.github/workflows/windows-phpt.ps1

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
param(
2+
[Parameter(Mandatory = $true)]
3+
[string[]] $Path,
4+
[int] $Offset = 0,
5+
[int] $Count = [int]::MaxValue,
6+
[int] $TimeoutSeconds = 12
7+
)
8+
9+
$ErrorActionPreference = 'Stop'
10+
$env:PHPT = '1'
11+
$env:TEST_PHP_EXECUTABLE = 'php'
12+
$env:SKIP_ONLINE_TESTS = '1'
13+
$env:NO_INTERACTION = '1'
14+
15+
$tests = foreach ($entry in $Path) {
16+
$item = Get-Item -LiteralPath $entry -ErrorAction SilentlyContinue
17+
if ($item -and $item.PSIsContainer) {
18+
Get-ChildItem -LiteralPath $item.FullName -Recurse -Filter *.phpt -File
19+
} elseif ($item) {
20+
$item
21+
} else {
22+
Get-ChildItem -Path $entry -Filter *.phpt -File
23+
}
24+
}
25+
26+
$tests = @(
27+
$tests |
28+
Sort-Object FullName -Unique |
29+
Select-Object -Skip $Offset -First $Count
30+
)
31+
32+
if ($tests.Count -eq 0) {
33+
throw "No PHPT files matched: $($Path -join ', ')"
34+
}
35+
36+
$innerTimeout = [Math]::Max(1, $TimeoutSeconds - 2)
37+
$options = @(
38+
'tests\run-tests',
39+
'-P',
40+
'-q',
41+
'-d', 'extension=php_swoole',
42+
'-d', 'swoole.use_shortname=On',
43+
'--show-diff',
44+
'-g', 'PASS,FAIL,BORK,LEAK,XLEAK',
45+
'--show-slow', '1000',
46+
'--set-timeout', $innerTimeout
47+
)
48+
49+
$failures = [System.Collections.Generic.List[string]]::new()
50+
$timeouts = [System.Collections.Generic.List[string]]::new()
51+
52+
foreach ($test in $tests) {
53+
$relativePath = Resolve-Path -Relative $test.FullName
54+
Write-Host "::group::PHPT $relativePath"
55+
try {
56+
$process = Start-Process -FilePath php -ArgumentList ($options + $test.FullName) -NoNewWindow -PassThru
57+
if (-not $process.WaitForExit($TimeoutSeconds * 1000)) {
58+
$timeouts.Add($relativePath)
59+
$failures.Add($relativePath)
60+
Write-Error "PHPT exceeded ${TimeoutSeconds}s: $relativePath" -ErrorAction Continue
61+
& taskkill.exe /PID $process.Id /T /F 2>$null | Out-Null
62+
$process.WaitForExit()
63+
} else {
64+
$process.Refresh()
65+
if ($process.ExitCode -ne 0) {
66+
$failures.Add($relativePath)
67+
Write-Error "PHPT failed with exit code $($process.ExitCode): $relativePath" -ErrorAction Continue
68+
}
69+
}
70+
} finally {
71+
Write-Host '::endgroup::'
72+
}
73+
}
74+
75+
Write-Host "Windows PHPT summary: $($tests.Count - $failures.Count) passed/skipped, $($failures.Count) failed, $($timeouts.Count) timed out"
76+
if ($failures.Count -gt 0) {
77+
Write-Host "Failed PHPT: $($failures -join ', ')"
78+
exit 1
79+
}

.github/workflows/windows.yml

Lines changed: 18 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
with:
5151
ext-path: .
5252
tools-path: C:\tools\phpdev
53-
conf-args: --enable-swoole-curl --enable-swoole-thread
53+
conf-args: --enable-swoole-curl --enable-swoole-thread --enable-php-sockets
5454
staging-deps: '1'
5555
deps: openssl,libcurl,libssh2,zlib,nghttp2,libzstd,brotli
5656

@@ -117,26 +117,18 @@ jobs:
117117
- name: Run Windows scheduler tests
118118
id: scheduler
119119
continue-on-error: true
120-
timeout-minutes: 1
121-
shell: cmd
120+
timeout-minutes: 3
121+
shell: powershell
122122
run: |
123-
set PHPT=1
124-
set TEST_PHP_EXECUTABLE=php
125-
set SKIP_ONLINE_TESTS=1
126-
set NO_INTERACTION=1
127-
php tests\run-tests -P -q -d extension=php_swoole -d swoole.use_shortname=On --show-diff -g PASS,FAIL,BORK,LEAK,XLEAK --show-slow 1000 --set-timeout 10 tests\swoole_coroutine_scheduler
123+
& .github\workflows\windows-phpt.ps1 -Path tests\swoole_coroutine_scheduler -TimeoutSeconds 12
128124
129125
- name: Run Windows coroutine utility tests
130126
id: coroutine-util
131127
continue-on-error: true
132-
timeout-minutes: 1
133-
shell: cmd
128+
timeout-minutes: 2
129+
shell: powershell
134130
run: |
135-
set PHPT=1
136-
set TEST_PHP_EXECUTABLE=php
137-
set SKIP_ONLINE_TESTS=1
138-
set NO_INTERACTION=1
139-
php tests\run-tests -P -q -d extension=php_swoole -d swoole.use_shortname=On --show-diff -g PASS,FAIL,BORK,LEAK,XLEAK --show-slow 1000 --set-timeout 10 tests\swoole_coroutine_util
131+
& .github\workflows\windows-phpt.ps1 -Path tests\swoole_coroutine_util -TimeoutSeconds 12
140132
141133
- name: Run Windows timer and event tests
142134
id: timer-event
@@ -153,74 +145,42 @@ jobs:
153145
- name: Run Windows IOCP socket tests (1/4)
154146
id: iocp-socket-1
155147
continue-on-error: true
156-
timeout-minutes: 1
148+
timeout-minutes: 2
157149
shell: powershell
158150
run: |
159-
$env:PHPT = '1'
160-
$env:TEST_PHP_EXECUTABLE = 'php'
161-
$env:SKIP_ONLINE_TESTS = '1'
162-
$env:NO_INTERACTION = '1'
163-
$options = @('-P', '-q', '-d', 'extension=php_swoole', '-d', 'swoole.use_shortname=On', '--show-diff', '-g', 'PASS,FAIL,BORK,LEAK,XLEAK', '--show-slow', '1000', '--set-timeout', '10')
164-
$tests = @(Get-ChildItem tests\swoole_socket_coro -Recurse -Filter *.phpt | Sort-Object FullName | Select-Object -First 16 | ForEach-Object { $_.FullName })
165-
& php tests\run-tests @options @tests
166-
exit $LASTEXITCODE
151+
& .github\workflows\windows-phpt.ps1 -Path tests\swoole_socket_coro -Count 16 -TimeoutSeconds 12
167152
168153
- name: Run Windows IOCP socket tests (2/4)
169154
id: iocp-socket-2
170155
continue-on-error: true
171-
timeout-minutes: 1
156+
timeout-minutes: 2
172157
shell: powershell
173158
run: |
174-
$env:PHPT = '1'
175-
$env:TEST_PHP_EXECUTABLE = 'php'
176-
$env:SKIP_ONLINE_TESTS = '1'
177-
$env:NO_INTERACTION = '1'
178-
$options = @('-P', '-q', '-d', 'extension=php_swoole', '-d', 'swoole.use_shortname=On', '--show-diff', '-g', 'PASS,FAIL,BORK,LEAK,XLEAK', '--show-slow', '1000', '--set-timeout', '10')
179-
$tests = @(Get-ChildItem tests\swoole_socket_coro -Recurse -Filter *.phpt | Sort-Object FullName | Select-Object -Skip 16 -First 16 | ForEach-Object { $_.FullName })
180-
& php tests\run-tests @options @tests
181-
exit $LASTEXITCODE
159+
& .github\workflows\windows-phpt.ps1 -Path tests\swoole_socket_coro -Offset 16 -Count 16 -TimeoutSeconds 12
182160
183161
- name: Run Windows IOCP socket tests (3/4)
184162
id: iocp-socket-3
185163
continue-on-error: true
186-
timeout-minutes: 1
164+
timeout-minutes: 2
187165
shell: powershell
188166
run: |
189-
$env:PHPT = '1'
190-
$env:TEST_PHP_EXECUTABLE = 'php'
191-
$env:SKIP_ONLINE_TESTS = '1'
192-
$env:NO_INTERACTION = '1'
193-
$options = @('-P', '-q', '-d', 'extension=php_swoole', '-d', 'swoole.use_shortname=On', '--show-diff', '-g', 'PASS,FAIL,BORK,LEAK,XLEAK', '--show-slow', '1000', '--set-timeout', '10')
194-
$tests = @(Get-ChildItem tests\swoole_socket_coro -Recurse -Filter *.phpt | Sort-Object FullName | Select-Object -Skip 32 -First 16 | ForEach-Object { $_.FullName })
195-
& php tests\run-tests @options @tests
196-
exit $LASTEXITCODE
167+
& .github\workflows\windows-phpt.ps1 -Path tests\swoole_socket_coro -Offset 32 -Count 16 -TimeoutSeconds 12
197168
198169
- name: Run Windows IOCP socket tests (4/4)
199170
id: iocp-socket-4
200171
continue-on-error: true
201-
timeout-minutes: 1
172+
timeout-minutes: 2
202173
shell: powershell
203174
run: |
204-
$env:PHPT = '1'
205-
$env:TEST_PHP_EXECUTABLE = 'php'
206-
$env:SKIP_ONLINE_TESTS = '1'
207-
$env:NO_INTERACTION = '1'
208-
$options = @('-P', '-q', '-d', 'extension=php_swoole', '-d', 'swoole.use_shortname=On', '--show-diff', '-g', 'PASS,FAIL,BORK,LEAK,XLEAK', '--show-slow', '1000', '--set-timeout', '10')
209-
$tests = @(Get-ChildItem tests\swoole_socket_coro -Recurse -Filter *.phpt | Sort-Object FullName | Select-Object -Skip 48 -First 16 | ForEach-Object { $_.FullName })
210-
& php tests\run-tests @options @tests
211-
exit $LASTEXITCODE
175+
& .github\workflows\windows-phpt.ps1 -Path tests\swoole_socket_coro -Offset 48 -Count 16 -TimeoutSeconds 12
212176
213177
- name: Run Windows thread tests
214178
id: thread
215179
continue-on-error: true
216-
timeout-minutes: 2
217-
shell: cmd
180+
timeout-minutes: 4
181+
shell: powershell
218182
run: |
219-
set PHPT=1
220-
set TEST_PHP_EXECUTABLE=php
221-
set SKIP_ONLINE_TESTS=1
222-
set NO_INTERACTION=1
223-
php tests\run-tests -P -q -d extension=php_swoole -d swoole.use_shortname=On --show-diff -g PASS,FAIL,BORK,LEAK,XLEAK --show-slow 1000 --set-timeout 10 tests\swoole_thread\*.phpt
183+
& .github\workflows\windows-phpt.ps1 -Path tests\swoole_thread\*.phpt -TimeoutSeconds 20
224184
225185
- name: Check Windows test results
226186
if: always()

ext-src/php_swoole_private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ BEGIN_EXTERN_C()
3535
#include <ext/standard/php_var.h>
3636
#include <ext/standard/basic_functions.h>
3737
#include <ext/standard/php_http.h>
38+
#include <main/php_network.h>
3839

3940
#define PHP_SWOOLE_VERSION SWOOLE_VERSION
4041

ext-src/php_swoole_thread.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ void php_swoole_thread_atomic_create(zval *return_value, ThreadResource *resourc
5555
void php_swoole_thread_atomic_long_create(zval *return_value, ThreadResource *resource);
5656
void php_swoole_thread_barrier_create(zval *return_value, ThreadResource *resource);
5757

58-
int php_swoole_thread_stream_cast(zval *zstream);
59-
void php_swoole_thread_stream_create(zval *return_value, zend_long sockfd);
58+
swSocketFd php_swoole_thread_stream_cast(zval *zstream);
59+
void php_swoole_thread_stream_create(zval *return_value, swSocketFd sockfd);
6060

61-
int php_swoole_thread_co_socket_cast(zval *zstream, swSocketType *type);
62-
void php_swoole_thread_co_socket_create(zval *return_value, zend_long sockfd, swSocketType type);
61+
swSocketFd php_swoole_thread_co_socket_cast(zval *zstream, swSocketType *type);
62+
void php_swoole_thread_co_socket_create(zval *return_value, swSocketFd sockfd, swSocketType type);
6363

6464
#define EMSG_NO_RESOURCE "resource not found"
6565
#define ECODE_NO_RESOURCE (-2)
@@ -108,7 +108,7 @@ struct ArrayItem {
108108
zend_long lval;
109109
double dval;
110110
struct {
111-
int fd;
111+
swSocketFd fd;
112112
swSocketType type;
113113
} socket;
114114
zend_string *serialized_object;

ext-src/swoole_thread.cc

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -579,39 +579,66 @@ void php_swoole_thread_bailout() {
579579
zend_bailout();
580580
}
581581

582-
int php_swoole_thread_stream_cast(zval *zstream) {
582+
static swSocketFd php_swoole_thread_socket_dup(swSocketFd sockfd) {
583+
#ifdef _WIN32
584+
WSAPROTOCOL_INFOW protocol_info;
585+
if (WSADuplicateSocketW(sockfd, GetCurrentProcessId(), &protocol_info) != 0) {
586+
errno = sw_socket_errno();
587+
return SW_BAD_SOCKET;
588+
}
589+
swSocketFd newfd = WSASocketW(
590+
FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, &protocol_info, 0, WSA_FLAG_OVERLAPPED);
591+
if (newfd == SW_BAD_SOCKET) {
592+
errno = sw_socket_errno();
593+
}
594+
return newfd;
595+
#else
596+
return dup(sockfd);
597+
#endif
598+
}
599+
600+
swSocketFd php_swoole_thread_stream_cast(zval *zstream) {
583601
php_stream *stream;
584-
int sockfd;
585-
int cast_flags = PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL;
602+
php_socket_t sockfd = SW_BAD_SOCKET;
603+
int cast_flags = PHP_STREAM_AS_SOCKETD | PHP_STREAM_CAST_INTERNAL;
586604
if ((php_stream_from_zval_no_verify(stream, zstream))) {
587-
if (php_stream_cast(stream, cast_flags, (void **) &sockfd, 1) == SUCCESS && sockfd >= 0) {
588-
return dup(sockfd);
605+
if (php_stream_cast(stream, cast_flags, (void **) &sockfd, 1) == SUCCESS && sockfd != SW_BAD_SOCKET) {
606+
return php_swoole_thread_socket_dup(sockfd);
589607
}
590608
}
591-
return -1;
609+
return SW_BAD_SOCKET;
592610
}
593611

594-
int php_swoole_thread_co_socket_cast(zval *zvalue, swSocketType *type) {
612+
swSocketFd php_swoole_thread_co_socket_cast(zval *zvalue, swSocketType *type) {
595613
SocketImpl *socket = php_swoole_get_socket(zvalue);
596614
if (!socket) {
597-
return -1;
615+
return SW_BAD_SOCKET;
598616
}
599-
int sockfd = socket->get_fd();
600-
if (sockfd < 0) {
601-
return -1;
617+
swSocketFd sockfd = socket->get_fd();
618+
if (sockfd == SW_BAD_SOCKET) {
619+
return SW_BAD_SOCKET;
602620
}
603-
int newfd = dup(sockfd);
604-
if (newfd < 0) {
605-
return -1;
621+
swSocketFd newfd = php_swoole_thread_socket_dup(sockfd);
622+
if (newfd == SW_BAD_SOCKET) {
623+
return SW_BAD_SOCKET;
606624
}
607625
*type = socket->get_type();
608626
return newfd;
609627
}
610628

611-
void php_swoole_thread_stream_create(zval *return_value, zend_long sockfd) {
629+
void php_swoole_thread_stream_create(zval *return_value, swSocketFd sockfd) {
630+
#ifdef _WIN32
631+
swSocketFd newfd = php_swoole_thread_socket_dup(sockfd);
632+
php_stream *stream =
633+
newfd == SW_BAD_SOCKET ? nullptr : php_stream_sock_open_from_socket((php_socket_t) newfd, nullptr);
634+
if (!stream && newfd != SW_BAD_SOCKET) {
635+
sw_close_socket(newfd);
636+
}
637+
#else
612638
std::string path = "php://fd/" + std::to_string(sockfd);
613639
// The file descriptor will be duplicated once here
614640
php_stream *stream = php_stream_open_wrapper_ex(path.c_str(), "", 0, NULL, NULL);
641+
#endif
615642
if (stream) {
616643
php_stream_to_zval(stream, return_value);
617644
} else {
@@ -620,8 +647,8 @@ void php_swoole_thread_stream_create(zval *return_value, zend_long sockfd) {
620647
}
621648
}
622649

623-
void php_swoole_thread_co_socket_create(zval *return_value, zend_long sockfd, swSocketType type) {
624-
swSocketFd newfd = (swSocketFd)dup((int)sockfd);
650+
void php_swoole_thread_co_socket_create(zval *return_value, swSocketFd sockfd, swSocketType type) {
651+
swSocketFd newfd = php_swoole_thread_socket_dup(sockfd);
625652
if (newfd == SW_BAD_SOCKET) {
626653
_error:
627654
object_init_ex(return_value, swoole_thread_error_ce);
@@ -637,17 +664,18 @@ void php_swoole_thread_co_socket_create(zval *return_value, zend_long sockfd, sw
637664
}
638665

639666
#ifdef SWOOLE_SOCKETS_SUPPORT
640-
void php_swoole_thread_php_socket_create(zval *return_value, zend_long sockfd) {
641-
int newfd = dup(sockfd);
642-
if (newfd < 0) {
667+
void php_swoole_thread_php_socket_create(zval *return_value, swSocketFd sockfd) {
668+
swSocketFd newfd = php_swoole_thread_socket_dup(sockfd);
669+
if (newfd == SW_BAD_SOCKET) {
643670
_error:
644671
object_init_ex(return_value, swoole_thread_error_ce);
645672
zend::object_set(return_value, ZEND_STRL("code"), errno);
646673
return;
647674
}
648675
object_init_ex(return_value, socket_ce);
649676
auto retsock = Z_SOCKET_P(return_value);
650-
if (!socket_import_file_descriptor(newfd, retsock)) {
677+
if (!socket_import_file_descriptor((php_socket_t) newfd, retsock)) {
678+
sw_close_socket(newfd);
651679
goto _error;
652680
}
653681
}
@@ -696,7 +724,7 @@ void ArrayItem::store(zval *zvalue) {
696724
case IS_RESOURCE: {
697725
value.socket.fd = php_swoole_thread_stream_cast(zvalue);
698726
type = IS_STREAM_SOCKET;
699-
if (value.socket.fd == -1) {
727+
if (value.socket.fd == SW_BAD_SOCKET) {
700728
zend_throw_exception(swoole_exception_ce, "failed to convert to socket fd", errno);
701729
}
702730
break;
@@ -710,7 +738,7 @@ void ArrayItem::store(zval *zvalue) {
710738
if (sw_zval_is_co_socket(zvalue)) {
711739
value.socket.fd = php_swoole_thread_co_socket_cast(zvalue, &value.socket.type);
712740
type = IS_CO_SOCKET;
713-
if (value.socket.fd == -1) {
741+
if (value.socket.fd == SW_BAD_SOCKET) {
714742
zend_throw_exception(swoole_exception_ce, "failed to convert to socket fd", errno);
715743
}
716744
break;
@@ -722,8 +750,8 @@ void ArrayItem::store(zval *zvalue) {
722750
zend_throw_exception(swoole_exception_ce, "invalid socket fd", EBADF);
723751
break;
724752
}
725-
value.socket.fd = dup(php_sock->bsd_socket);
726-
if (value.socket.fd == -1) {
753+
value.socket.fd = php_swoole_thread_socket_dup(php_sock->bsd_socket);
754+
if (value.socket.fd == SW_BAD_SOCKET) {
727755
zend_throw_exception(swoole_exception_ce, "failed to dup socket fd", errno);
728756
}
729757
type = IS_PHP_SOCKET;
@@ -971,8 +999,8 @@ void ArrayItem::release() {
971999
zend_string_release(value.str);
9721000
value.str = nullptr;
9731001
} else if (type == IS_STREAM_SOCKET || type == IS_CO_SOCKET || type == IS_PHP_SOCKET) {
974-
close(value.socket.fd);
975-
value.socket.fd = -1;
1002+
sw_close_socket(value.socket.fd);
1003+
value.socket.fd = SW_BAD_SOCKET;
9761004
} else if (type == IS_SERIALIZED_OBJECT) {
9771005
zend_string_release(value.serialized_object);
9781006
value.serialized_object = nullptr;

0 commit comments

Comments
 (0)