xdebug / vscode-php-debug

PHP Debug Adapter for Visual Studio Code 🐞⛔
MIT License
763 stars 178 forks source link

[SOLVED] Breakpoints are greyed out for being changed, while they were not #919

Closed nikitades closed 11 months ago

nikitades commented 11 months ago

PHP version: 8.2 Xdebug version: 3.2.2 VS Code extension version: 1.33.0 OS: Mac OS ARM

Screenshots of 1st run and subsequent 2nd run First run image
Your launch.json: ```json { "version": "0.2.0", "configurations": [ { "name": "Listen for Xdebug", "type": "php", "request": "launch", "preLaunchTask": "Enable XDebug", "postDebugTask": "Disable XDebug", "cwd": "${workspaceRoot}/src", "log": true }, { "name": "Launch Built-in web server", "type": "php", "request": "launch", "runtimeArgs": [ "-dxdebug.mode=debug", "-dxdebug.start_with_request=yes", "-S", "localhost:8080" ], "program": "", "cwd": "${workspaceRoot}/src", "port": 9003, "serverReadyAction": { "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", "uriFormat": "http://localhost:%s", "action": "openExternally" }, "presentation": { "hidden": false, "group": "", "order": 1 } } ] } ```
Xdebug php.ini config: ```ini xdebug.mode=debug xdebug.client_host=0.0.0.0 xdebug.client_port=9003 xdebug.log=/Users/ars/xdebug.log ```
Xdebug logfile (from setting `xdebug.log` in php.ini): ``` [38971] Log opened at 2023-07-29 13:57:22.457100 [38971] [Step Debug] INFO: Connecting to configured address/port: 0.0.0.0:9003. [38971] [Step Debug] INFO: Connected to debugging client: 0.0.0.0:9003 (through xdebug.client_host/xdebug.client_port). [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 1 -n resolved_breakpoints -v 1 [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 2 -n notify_ok -v 1 [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 3 -n extended_properties -v 1 [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 4 -n breakpoint_include_return_value -v 1 [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 5 -n max_children -v 100 [38971] [Step Debug] -> [38971] [Step Debug] <- breakpoint_set -i 6 -t line -f file:///Users/ars/Code/Personal/php-debug-test/local/src/index.php -n 3 [38971] [Step Debug] -> [38971] [Step Debug] -> [38971] [Step Debug] <- breakpoint_get -i 7 -d 389710001 [38971] [Step Debug] -> [38971] [Step Debug] <- breakpoint_set -i 8 -t line -f file:///Users/ars/Code/Personal/php-debug-test/local/src/index.php -n 7 [38971] [Step Debug] -> [38971] [Step Debug] -> [38971] [Step Debug] <- breakpoint_get -i 9 -d 389710002 [38971] [Step Debug] -> [38971] [Step Debug] <- breakpoint_set -i 10 -t exception -x * [38971] [Step Debug] -> [38971] [Step Debug] <- run -i 11 [38971] [Step Debug] -> [38971] [Step Debug] <- stack_get -i 12 [38971] [Step Debug] -> [38971] [Step Debug] <- context_names -i 13 -d 0 [38971] [Step Debug] -> [38971] [Step Debug] <- context_get -i 14 -d 0 -c 0 [38971] [Step Debug] -> [38971] [Step Debug] <- run -i 15 [38971] [Step Debug] -> [38971] [Step Debug] <- stack_get -i 16 [38971] [Step Debug] -> [38971] [Step Debug] <- context_names -i 17 -d 0 [38971] [Step Debug] -> [38971] [Step Debug] <- context_get -i 18 -d 0 -c 0 [38971] [Step Debug] -> [38971] [Step Debug] <- run -i 19 [38971] [Step Debug] -> [38971] [Step Debug] <- stop -i 20 [38971] [Step Debug] -> [38971] Log closed at 2023-07-29 13:57:24.123212 [38971] Log opened at 2023-07-29 13:57:25.463875 [38971] [Step Debug] INFO: Connecting to configured address/port: 0.0.0.0:9003. [38971] [Step Debug] INFO: Connected to debugging client: 0.0.0.0:9003 (through xdebug.client_host/xdebug.client_port). [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 1 -n resolved_breakpoints -v 1 [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 2 -n notify_ok -v 1 [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 3 -n extended_properties -v 1 [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 4 -n breakpoint_include_return_value -v 1 [38971] [Step Debug] -> [38971] [Step Debug] <- feature_set -i 5 -n max_children -v 100 [38971] [Step Debug] -> [38971] [Step Debug] <- breakpoint_set -i 6 -t line -f file:///Users/ars/Code/Personal/php-debug-test/local/src/index.php -n 3 [38971] [Step Debug] -> [38971] [Step Debug] <- breakpoint_set -i 7 -t line -f file:///Users/ars/Code/Personal/php-debug-test/local/src/index.php -n 7 [38971] [Step Debug] -> [38971] [Step Debug] <- breakpoint_set -i 8 -t exception -x * [38971] [Step Debug] -> [38971] [Step Debug] <- run -i 9 [38971] [Step Debug] -> [38971] [Step Debug] <- stack_get -i 10 [38971] [Step Debug] -> [38971] [Step Debug] <- context_names -i 11 -d 0 [38971] [Step Debug] -> [38971] [Step Debug] <- context_get -i 12 -d 0 -c 0 [38971] [Step Debug] -> [38971] [Step Debug] <- run -i 13 [38971] [Step Debug] -> [38971] [Step Debug] <- stack_get -i 14 [38971] [Step Debug] -> [38971] [Step Debug] <- run -i 15 [38971] [Step Debug] -> [38971] [Step Debug] <- stop -i 16 [38971] [Step Debug] -> [38971] Log closed at 2023-07-29 13:57:26.469046 ```
VS Code extension logfile (from setting `"log": true` in launch.json): ``` Listening on { address: '::', family: 'IPv6', port: 9003 } <- launchResponse Response { seq: 0, type: 'response', request_seq: 2, command: 'launch', success: true } <- initializedEvent InitializedEvent { seq: 0, type: 'event', event: 'initialized' } -> setBreakpointsRequest { command: 'setBreakpoints', arguments: { source: { name: 'index.php', path: '/Users/ars/Code/Personal/php-debug-test/local/src/index.php' }, lines: [ 3, 7 ], breakpoints: [ { line: 3 }, { line: 7 } ], sourceModified: false }, type: 'request', seq: 3 } <- setBreakpointsResponse Response { seq: 0, type: 'response', request_seq: 3, command: 'setBreakpoints', success: true, body: { breakpoints: [ { verified: true, line: 3, source: { name: 'index.php', path: '/Users/ars/Code/Personal/php-debug-test/local/src/index.php' }, id: 1 }, { verified: true, line: 7, source: { name: 'index.php', path: '/Users/ars/Code/Personal/php-debug-test/local/src/index.php' }, id: 2 } ] } } -> setFunctionBreakpointsRequest { command: 'setFunctionBreakpoints', arguments: { breakpoints: [] }, type: 'request', seq: 4 } <- setFunctionBreakpointsResponse Response { seq: 0, type: 'response', request_seq: 4, command: 'setFunctionBreakpoints', success: true, body: { breakpoints: [] } } -> setExceptionBreakpointsRequest { command: 'setExceptionBreakpoints', arguments: { filters: [ '*' ] }, type: 'request', seq: 5 } <- setExceptionBreakpointsResponse Response { seq: 0, type: 'response', request_seq: 5, command: 'setExceptionBreakpoints', success: true, body: { breakpoints: [ { verified: true, id: 3 } ] } } -> configurationDoneRequest { command: 'configurationDone', type: 'request', seq: 6 } <- configurationDoneResponse Response { seq: 0, type: 'response', request_seq: 6, command: 'configurationDone', success: true } -> threadsRequest { command: 'threads', type: 'request', seq: 7 } <- threadsResponse Response { seq: 0, type: 'response', request_seq: 7, command: 'threads', success: true, body: { threads: [] } } new connection 1 from ::ffff:127.0.0.1 xd(1) -> xd(1) <- feature_set -i 1 -n resolved_breakpoints -v 1 xd(1) -> xd(1) <- feature_set -i 2 -n notify_ok -v 1 xd(1) -> xd(1) <- feature_set -i 3 -n extended_properties -v 1 xd(1) -> xd(1) <- feature_set -i 4 -n breakpoint_include_return_value -v 1 xd(1) -> xd(1) <- feature_set -i 5 -n max_children -v 100 xd(1) -> <- threadEvent ThreadEvent { seq: 0, type: 'event', event: 'thread', body: { reason: 'started', threadId: 1 } } xd(1) <- breakpoint_set -i 6 -t line -f file:///Users/ars/Code/Personal/php-debug-test/local/src/index.php -n 3 xd(1) -> xd(1) -> xd(1) <- breakpoint_get -i 7 -d 389710001 xd(1) -> <- breakpointEvent BreakpointEvent { seq: 0, type: 'event', event: 'breakpoint', body: { reason: 'changed', breakpoint: { id: 1, verified: true, line: 3 } } } xd(1) <- breakpoint_set -i 8 -t line -f file:///Users/ars/Code/Personal/php-debug-test/local/src/index.php -n 7 xd(1) -> xd(1) -> xd(1) <- breakpoint_get -i 9 -d 389710002 xd(1) -> <- breakpointEvent BreakpointEvent { seq: 0, type: 'event', event: 'breakpoint', body: { reason: 'changed', breakpoint: { id: 2, verified: true, line: 7 } } } xd(1) <- breakpoint_set -i 10 -t exception -x * xd(1) -> <- breakpointEvent BreakpointEvent { seq: 0, type: 'event', event: 'breakpoint', body: { reason: 'changed', breakpoint: { id: 3, verified: true } } } xd(1) <- run -i 11 xd(1) -> <- stoppedEvent StoppedEvent { seq: 0, type: 'event', event: 'stopped', body: { reason: 'breakpoint', threadId: 1, allThreadsStopped: false } } -> threadsRequest { command: 'threads', type: 'request', seq: 8 } <- threadsResponse Response { seq: 0, type: 'response', request_seq: 8, command: 'threads', success: true, body: { threads: [ Thread { id: 1, name: 'Request 1 (15:57:22)' } ] } } -> stackTraceRequest { command: 'stackTrace', arguments: { threadId: 1, startFrame: 0, levels: 20 }, type: 'request', seq: 9 } xd(1) <- stack_get -i 12 xd(1) -> <- stackTraceResponse Response { seq: 0, type: 'response', request_seq: 9, command: 'stackTrace', success: true, body: { totalFrames: 1, stackFrames: [ { id: 1, name: '{main}', source: { name: 'index.php', path: '/Users/ars/Code/Personal/php-debug-test/local/src/index.php' }, line: 3, column: 1 } ] } } -> threadsRequest { command: 'threads', type: 'request', seq: 10 } <- threadsResponse Response { seq: 0, type: 'response', request_seq: 10, command: 'threads', success: true, body: { threads: [ Thread { id: 1, name: 'Request 1 (15:57:22)' } ] } } -> scopesRequest { command: 'scopes', arguments: { frameId: 1 }, type: 'request', seq: 11 } xd(1) <- context_names -i 13 -d 0 xd(1) -> <- scopesResponse Response { seq: 0, type: 'response', request_seq: 11, command: 'scopes', success: true, body: { scopes: [ Scope { name: 'Locals', variablesReference: 1, expensive: false }, Scope { name: 'Superglobals', variablesReference: 2, expensive: false }, Scope { name: 'User defined constants', variablesReference: 3, expensive: false } ] } } -> variablesRequest { command: 'variables', arguments: { variablesReference: 1 }, type: 'request', seq: 12 } xd(1) <- context_get -i 14 -d 0 -c 0 xd(1) -> <- variablesResponse Response { seq: 0, type: 'response', request_seq: 12, command: 'variables', success: true, body: { variables: [ { name: '$a', value: 'uninitialized', type: 'uninitialized', variablesReference: 0, presentationHint: {}, evaluateName: '$a', indexedVariables: undefined }, { name: '$b', value: 'uninitialized', type: 'uninitialized', variablesReference: 0, presentationHint: {}, evaluateName: '$b', indexedVariables: undefined } ] } } -> continueRequest { command: 'continue', arguments: { threadId: 1 }, type: 'request', seq: 13 } <- continueResponse Response { seq: 0, type: 'response', request_seq: 13, command: 'continue', success: true, body: { allThreadsContinued: false } } xd(1) <- run -i 15 xd(1) -> <- stoppedEvent StoppedEvent { seq: 0, type: 'event', event: 'stopped', body: { reason: 'breakpoint', threadId: 1, allThreadsStopped: false } } -> threadsRequest { command: 'threads', type: 'request', seq: 14 } <- threadsResponse Response { seq: 0, type: 'response', request_seq: 14, command: 'threads', success: true, body: { threads: [ Thread { id: 1, name: 'Request 1 (15:57:22)' } ] } } -> stackTraceRequest { command: 'stackTrace', arguments: { threadId: 1, startFrame: 0, levels: 20 }, type: 'request', seq: 15 } xd(1) <- stack_get -i 16 xd(1) -> <- stackTraceResponse Response { seq: 0, type: 'response', request_seq: 15, command: 'stackTrace', success: true, body: { totalFrames: 1, stackFrames: [ { id: 2, name: '{main}', source: { name: 'index.php', path: '/Users/ars/Code/Personal/php-debug-test/local/src/index.php' }, line: 7, column: 1 } ] } } -> scopesRequest { command: 'scopes', arguments: { frameId: 2 }, type: 'request', seq: 16 } xd(1) <- context_names -i 17 -d 0 xd(1) -> <- scopesResponse Response { seq: 0, type: 'response', request_seq: 16, command: 'scopes', success: true, body: { scopes: [ Scope { name: 'Locals', variablesReference: 4, expensive: false }, Scope { name: 'Superglobals', variablesReference: 5, expensive: false }, Scope { name: 'User defined constants', variablesReference: 6, expensive: false } ] } } -> variablesRequest { command: 'variables', arguments: { variablesReference: 4 }, type: 'request', seq: 17 } xd(1) <- context_get -i 18 -d 0 -c 0 xd(1) -> <- variablesResponse Response { seq: 0, type: 'response', request_seq: 17, command: 'variables', success: true, body: { variables: [ { name: '$a', value: '3', type: 'int', variablesReference: 0, presentationHint: {}, evaluateName: '$a', indexedVariables: undefined }, { name: '$b', value: '3', type: 'int', variablesReference: 0, presentationHint: {}, evaluateName: '$b', indexedVariables: undefined } ] } } -> continueRequest { command: 'continue', arguments: { threadId: 1 }, type: 'request', seq: 18 } <- continueResponse Response { seq: 0, type: 'response', request_seq: 18, command: 'continue', success: true, body: { allThreadsContinued: false } } xd(1) <- run -i 19 xd(1) -> xd(1) <- stop -i 20 xd(1) -> <- threadEvent ThreadEvent { seq: 0, type: 'event', event: 'thread', body: { reason: 'exited', threadId: 1 } } new connection 2 from ::ffff:127.0.0.1 xd(2) -> xd(2) <- feature_set -i 1 -n resolved_breakpoints -v 1 xd(2) -> xd(2) <- feature_set -i 2 -n notify_ok -v 1 xd(2) -> xd(2) <- feature_set -i 3 -n extended_properties -v 1 xd(2) -> xd(2) <- feature_set -i 4 -n breakpoint_include_return_value -v 1 xd(2) -> xd(2) <- feature_set -i 5 -n max_children -v 100 xd(2) -> <- threadEvent ThreadEvent { seq: 0, type: 'event', event: 'thread', body: { reason: 'started', threadId: 2 } } xd(2) <- breakpoint_set -i 6 -t line -f file:///Users/ars/Code/Personal/php-debug-test/local/src/index.php -n 3 xd(2) -> <- breakpointEvent BreakpointEvent { seq: 0, type: 'event', event: 'breakpoint', body: { reason: 'changed', breakpoint: { id: 1, verified: false } } } xd(2) <- breakpoint_set -i 7 -t line -f file:///Users/ars/Code/Personal/php-debug-test/local/src/index.php -n 7 xd(2) -> <- breakpointEvent BreakpointEvent { seq: 0, type: 'event', event: 'breakpoint', body: { reason: 'changed', breakpoint: { id: 2, verified: false } } } xd(2) <- breakpoint_set -i 8 -t exception -x * xd(2) -> <- breakpointEvent BreakpointEvent { seq: 0, type: 'event', event: 'breakpoint', body: { reason: 'changed', breakpoint: { id: 3, verified: true } } } xd(2) <- run -i 9 xd(2) -> <- stoppedEvent StoppedEvent { seq: 0, type: 'event', event: 'stopped', body: { reason: 'breakpoint', threadId: 2, allThreadsStopped: false } } -> threadsRequest { command: 'threads', type: 'request', seq: 19 } <- threadsResponse Response { seq: 0, type: 'response', request_seq: 19, command: 'threads', success: true, body: { threads: [ Thread { id: 2, name: 'Request 2 (15:57:25)' } ] } } -> stackTraceRequest { command: 'stackTrace', arguments: { threadId: 2, startFrame: 0, levels: 20 }, type: 'request', seq: 20 } xd(2) <- stack_get -i 10 xd(2) -> <- stackTraceResponse Response { seq: 0, type: 'response', request_seq: 20, command: 'stackTrace', success: true, body: { totalFrames: 1, stackFrames: [ { id: 3, name: '{main}', source: { name: 'index.php', path: '/Users/ars/Code/Personal/php-debug-test/local/src/index.php' }, line: 3, column: 1 } ] } } -> threadsRequest { command: 'threads', type: 'request', seq: 21 } <- threadsResponse Response { seq: 0, type: 'response', request_seq: 21, command: 'threads', success: true, body: { threads: [ Thread { id: 2, name: 'Request 2 (15:57:25)' } ] } } -> scopesRequest { command: 'scopes', arguments: { frameId: 3 }, type: 'request', seq: 22 } xd(2) <- context_names -i 11 -d 0 xd(2) -> <- scopesResponse Response { seq: 0, type: 'response', request_seq: 22, command: 'scopes', success: true, body: { scopes: [ Scope { name: 'Locals', variablesReference: 7, expensive: false }, Scope { name: 'Superglobals', variablesReference: 8, expensive: false }, Scope { name: 'User defined constants', variablesReference: 9, expensive: false } ] } } -> variablesRequest { command: 'variables', arguments: { variablesReference: 7 }, type: 'request', seq: 23 } xd(2) <- context_get -i 12 -d 0 -c 0 xd(2) -> <- variablesResponse Response { seq: 0, type: 'response', request_seq: 23, command: 'variables', success: true, body: { variables: [ { name: '$a', value: 'uninitialized', type: 'uninitialized', variablesReference: 0, presentationHint: {}, evaluateName: '$a', indexedVariables: undefined }, { name: '$b', value: 'uninitialized', type: 'uninitialized', variablesReference: 0, presentationHint: {}, evaluateName: '$b', indexedVariables: undefined } ] } } -> continueRequest { command: 'continue', arguments: { threadId: 2 }, type: 'request', seq: 24 } <- continueResponse Response { seq: 0, type: 'response', request_seq: 24, command: 'continue', success: true, body: { allThreadsContinued: false } } xd(2) <- run -i 13 xd(2) -> <- stoppedEvent StoppedEvent { seq: 0, type: 'event', event: 'stopped', body: { reason: 'breakpoint', threadId: 2, allThreadsStopped: false } } -> threadsRequest { command: 'threads', type: 'request', seq: 25 } <- threadsResponse Response { seq: 0, type: 'response', request_seq: 25, command: 'threads', success: true, body: { threads: [ Thread { id: 2, name: 'Request 2 (15:57:25)' } ] } } -> stackTraceRequest { command: 'stackTrace', arguments: { threadId: 2, startFrame: 0, levels: 20 }, type: 'request', seq: 26 } xd(2) <- stack_get -i 14 xd(2) -> <- stackTraceResponse Response { seq: 0, type: 'response', request_seq: 26, command: 'stackTrace', success: true, body: { totalFrames: 1, stackFrames: [ { id: 4, name: '{main}', source: { name: 'index.php', path: '/Users/ars/Code/Personal/php-debug-test/local/src/index.php' }, line: 7, column: 1 } ] } } -> continueRequest { command: 'continue', arguments: { threadId: 2 }, type: 'request', seq: 27 } <- continueResponse Response { seq: 0, type: 'response', request_seq: 27, command: 'continue', success: true, body: { allThreadsContinued: false } } xd(2) <- run -i 15 xd(2) -> xd(2) <- stop -i 16 xd(2) -> <- threadEvent ThreadEvent { seq: 0, type: 'event', event: 'thread', body: { reason: 'exited', threadId: 2 } } -> disconnectRequest { command: 'disconnect', arguments: { restart: false, terminateDebuggee: true }, type: 'request', seq: 28 } <- disconnectResponse Response { seq: 0, type: 'response', request_seq: 28, command: 'disconnect', success: true } ```

Code snippet to reproduce:

<?php

(breakpoint) $a = 1;

$b = $a += 2;

(breakpoint) echo $b;
zobo commented 11 months ago

Hi. Thanks for the report.. Will dive into the logs to see why the second set of breakpoints isn't resolved.

zobo commented 11 months ago

Hi. Very strange. How are you running PHP? Is it as a dev server (php -S), FPM, some other way?

zobo commented 11 months ago

Hey @derickr, can you also look at this xdebug.log. Here we have two consecutive Xdebug sessions, on the same PHP process (same PID). Breakpoints being set on the same lines on both. The line breakpoints from the first session get resolved (389710001, 389710002) the breakpoints in the second session however do not get resolved (389710004, 389710005).

nikitades commented 11 months ago

Hi @zobo. I run it as the dev server. Docker- and fpm-based version shows stable breakpoint resolving, however.

zobo commented 11 months ago

Hm. Can you also provide you phpinfo(). I'm interested if you are running NTS or TS mode of PHP. I'm currently testing on windows, but could be specific to Mac.

zobo commented 11 months ago

Additional question. Which of the two configurations from launch.json are you using "Listen for Xdebug" or "Launch Built-in web server"? If you are using the first, how exactly are you starting the dev server, can you give me the command? Also the php code you provided. Is it in a file that is directly run or is it included with a require() or include()? Thanks!

nikitades commented 11 months ago
  1. phpinfo (sorry for this ridiculous format, I've got no better idea than that): PHP 8.2.8 - phpinfo().pdf (thread safety disabled)

  2. Actually both configurations of launch.json reproduce the original issue. However, at the time of the posting I used the first one. Which means, I do the following steps:

    • enable xdebug by adding xdebug.start_with_request=yes to xdebug.ini (the rest of it is in the original post)
    • switch on the debug mode of VSCode by pressing F5
    • launch the dev server by issuing php -S 0.0.0.0:8080
    • put some breakpoints
    • open localhost:8080
    • open it again, see that breakpoints are greyed out starting from the 2nd request
derickr commented 11 months ago

Hi!

TL,DR: Not a bug, and (shock horror) documented behaviour.

I spend some time delving into this, and at first I could not reproduce this. On my side (Linux, PHP 8.1/8.2) with php -S the behaviour was always right, with the breakpoints being resolved for each request through the dev server.

When I had another good look at your phpinfo() output PDF file, I noticed:

Zend Engine v4.2.8, Copyright (c) Zend Technologies
    with Xdebug v3.2.2, Copyright (c) 2002-2023, by Derick Rethans
    with Zend OPcache v8.2.8, Copyright (c), by Zend Technologies

This has OPcache loaded after Xdebug, which the documentation says you shouldn't do:

Zend Opcache

    Can be loaded together with Xdebug, but it is not 100% compatible.

    Load Xdebug after Opcache in php.ini for better compatibility. When running php -v or when looking at phpinfo(); output, Xdebug should be listed below Opcache.

And indeed, when I switched the loading order of the two zend extensions from:

XDEBUG_MODE=debug XDEBUG_TRIGGER=yes php -n -d zend_extension=opcache -d zend_extension=xdebug -S localhost:9112 -t /tmp

to

XDEBUG_MODE=debug XDEBUG_TRIGGER=yes php -n -d zend_extension=xdebug -d zend_extension=opcache -S localhost:9112 -t /tmp

I could reproduce this issue.

Both Xdebug and OPcache override PHP's "compile a file" handler.

Xdebug's uses this so that it can analyse newly loaded files for lines of code that can have breakpoints on it, so that it then can then resolve them. Before doing it's magic, it calls the already present handler.

OPcache uses it to see if a file that is being parsed for a second time, and it is in its cache, it doesn't compile it again by not calling the already present handler.

If OPcache is first loaded, and then Xdebug, the following happens:

This means that when PHP runs the "compile this file" logic, it first calls xdebug_compile_file, which then calls opcache_compile_file and all is well.

If OPcache is loaded last, the process is reversed:

When PHP then runs the "compile this file" logic, it calls opcache_compile first. This checks whether it has seen the file already, and if not, calls the previous handler (xdebug_compile_file), but if it has seen the file already (the second request through a php -S server) it does not call the previous compile file handler. Normally, that is what you want, as compiling files is expensive. However, because it does not call the previous file handler, that means that xdebug_compile_file does not get run, which in turn means it doesn't know anything about which lines of code can have breakpoints on them.

This is not something that Xdebug can fix.

There are workarounds:

Curiously, your phpinfo() output allegedly already shows the right order (/opt/homebrew/etc/php/8.2/conf.d/ext-opcache.ini, /opt/homebrew/etc/php/8.2/conf.d/xdebug.ini), but I can not tell if your PHP actually loads them in the order — the phpinfo() output indicates that it doesn't — or whether there is another place where the extension gets loaded (sometimes launch.json adds the -dzend_extension=xdebug part. Personally, I usually name the xdebug.ini file 99-xdebug.ini to enforce the sorting order — it is also possible that OSX's file system does not support an actual ordering here. If that is the case, you should create one ini file which loads both extensions:

zend_extension=opcache
zend_extension=xdebug

I will see if I can add a warning for this to Xdebug's diagnostics log.

nikitades commented 11 months ago

Hello @derickr! Thanks for such a detail reply. I'll check whether it's my case ASAP. 🤝

nikitades commented 11 months ago

HOW TO FIX:

OR

A word of gratitude:

I can confirm that after I have

(VSCode-initiated server launch profile also is stable, since it relies on the system order of extensions loading)

(php --version output does not correlate with the actual extension load order)

@derickr Please have my respect about such a brilliant investigation! @zobo Thank you ❤️

Closing the issue.

derickr commented 11 months ago

I'm adding a warning too: https://github.com/xdebug/xdebug/pull/901, and there is now more documentation: https://xdebug.org/docs/errors#DBG-W-OPCACHE to go with that log warning.