GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GH-103082: Implementation of PEP 669: Low Impact Monitoring for CPython by markshannon · Pull Request #103083 · python/cpython

Conversation

@markshannon

@markshannon markshannon commented Mar 28, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

This implements PEP 669.
There are a couple of things missing, but no harm in early review.

  • Issue: Implement and document PEP 669. #103082

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

artemmukhin and erlend-aasland reacted with rocket emoji
All reactions
  • 🚀 2 reactions
markshannon added 30 commits November 11, 2022 12:22
@markshannon
Initial experimental implementation of PEP 669.
e85d910
@markshannon
First draft implementing small subset of PEP 669.
6283ee8
@markshannon
Support disabling and restarting.
4edb7b7
@markshannon
Support multiple tools per event.
852c40b
@markshannon
Tidy up of monitoring internals
416b314
@markshannon
Fix legacy tracing.
7971979
@markshannon
Implement support for multiple tools.
9896902
@markshannon
Get support for line tracing mostly working. Needs to wait for regist…
f432a66
…er VM.
@markshannon
Merge branch 'main' into pep-669-incremental
fb29b34
@markshannon
Fix up INSTRUMENTED_OPCODES vector.
15a1ccd
@markshannon
Fix instrumented branches to call instrumentation with correct target.
9abb339
@markshannon
Merge main and assorted fixups to handle new instruction.
3ba6a39
@markshannon
Add PY_THROW event handling and fix up line table.
aa09895
@markshannon
LINE events working for sys.setrace.
9e5d87d
@markshannon
Add lots of internal debugging for instrumentation.
e52cbe6
@markshannon
Add more tests. Get those and some other passing.
6c8be7e
@markshannon
Fix LINE instrumentation and frame.set_lineno support (mostly)
c9e1e21
@markshannon
Refining line event generation.
e52e8d3
@markshannon
Get sys.settrace tests passing.
f434ec7
@markshannon
Monitor 'internal' StopIteration raises.
b680084
@markshannon
Check for NULLs.
e6e7cf1
@markshannon
Fix up a few tests
5bbc83e
@markshannon
Turn off debugging output by default.
7fe9a43
@markshannon
Remove debugging printfs
8b9f996
@markshannon
Avoid refleak.
9b02640
@markshannon
Record last traced line on frame object.
2cadf32
@markshannon
Get a couple more top-level tests passing.
1f54d77
@markshannon
Update magic number
43a3f3e
@markshannon
Remove debug print statement.
3d436cf
@markshannon
Raise SystemError if frame is missing.
691bcf5
@gvanrossum

gvanrossum commented Apr 11, 2023

Copy link
Copy Markdown
Member

Is there another module that behaves like this?

import sys

print(type(sys.monitoring))

import sys.monitoring

produces:

<class 'module'>
Traceback (most recent call last):
  File "/Users/nedbatchelder/coverage/trunk/../lab/pep669.py", line 5, in <module>
    import sys.monitoring
ModuleNotFoundError: No module named  'sys.monitoring'; 'sys' is not a package

It's a module but I can't import it directly.

But does it matter? There is no promise that sys.monitoring is a module. It's just a namespace ("let's do more of those!"). You can write import sys; sys.monitoring.set_events(...) or you can use from sys import modules; modules.set_events(...). I'd say having it be a module is better than having it be a class full of static methods. :-)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@brandtbucher

brandtbucher commented Apr 11, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Is there another module that behaves like this?

import sys

print(type(sys.monitoring))

import sys.monitoring

produces:

<class 'module'>
Traceback (most recent call last):
  File "/Users/nedbatchelder/coverage/trunk/../lab/pep669.py", line 5, in <module>
    import sys.monitoring
ModuleNotFoundError: No module named  'sys.monitoring'; 'sys' is not a package

It's a module but I can't import it directly.

This happens basically whenever a module imports another module:

>>> import ast
>>> type(ast.sys)
<class 'module'>
>>> import ast.sys
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named  'ast.sys'; 'ast' is not a package

🙃

Perhaps a good mental model for sys.monitoring is that the top line of the sys module is import _something_super_secret as monitoring.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon

markshannon commented Apr 12, 2023

Copy link
Copy Markdown
Member Author

It is a bit bit weird that from sys import monitoring as m doesn't work.
We should probably fix that.

I expect to (hopefully before 3.12) use some sort of lazy loading to avoid creating the monitoring object if it isn't needed.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon

markshannon commented Apr 12, 2023

Copy link
Copy Markdown
Member Author

@fabioz Thanks for the reproducer.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

markshannon added 2 commits April 12, 2023 11:02
@markshannon
Reset last traced line number when setting frame.f_trace only if set …
168b34a
…to new value.
@markshannon
Tidy up test case.
f07a080
@markshannon

markshannon commented Apr 12, 2023

Copy link
Copy Markdown
Member Author

OK. I'm merging this.

I think this is stable enough to merge, and we can probably get better bug reports with this merged than on a branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon
markshannon merged commit 411b169 into python:main Apr 12, 2023
@bedevere-bot bedevere-bot removed the awaiting merge label Apr 12, 2023
@fabioz

fabioz commented Apr 12, 2023

Copy link
Copy Markdown
Contributor

@markshannon from the fix you seem to have put there, it'd still fail if the user set the tracing to None and then back to the actual trace function...

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon

markshannon commented Apr 12, 2023

Copy link
Copy Markdown
Member Author

I think that is the current behavior, is it not?

If you restart tracing, then you want a line event for the current line.
At least, pdb seems to want that. If I don't clear the "last traced line", when setting f_trace, then the pdb tests fail.

TBH, it is all an undocumented black box, so some guess work is required.
It seems the best we can do is add test cases, so if you have any more tests I'd be grateful.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@erlend-aasland

erlend-aasland commented Apr 12, 2023

Copy link
Copy Markdown
Contributor

Darn, I was just about to complete my second review.

$ ./python.exe -m test -R : test_monitoring  # <= fails
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

erlend-aasland
erlend-aasland reviewed Apr 12, 2023
View reviewed changes
Comment thread Include/internal/pycore_frame.h
Comment on lines +142 to +144
Having stacktop <= 0 ensures that invalid
values are not visible to the cycle GC.
We choose -1 rather than 0 to assist debugging. */

@erlend-aasland erlend-aasland Apr 6, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
Having stacktop <= 0 ensures that invalid
values are not visible to the cycle GC.
We choose -1 rather than 0 to assist debugging. */
Having stacktop <= 0 ensures that invalid
values are not visible to the cycle GC.
We choose -1 rather than 0 to assist debugging. */

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/ceval.c
DTRACE_FUNCTION_ENTRY();
/* Because this avoids the RESUME,
* we need to update instrumentation */
_Py_Instrument(frame->f_code, tstate->interp);

@erlend-aasland erlend-aasland Apr 6, 2023

Copy link
Copy Markdown
Contributor

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.

The return value of _Py_Instrument is ignored here. Since it might set an exception, I'd expect a goto exit_unwind on error here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@markshannon markshannon Apr 12, 2023

Copy link
Copy Markdown
Member Author

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.

We are already handling an exception here, so the exception isn't lost, it replaces the thrown exception.
The question is whether to raise it back to the caller, or to inject into the coroutine/generator?

If unwinding were done in a separate function from evaluation, then the thrown exception would unwind the stack, then the new exception would be raised on continued execution, which would be better.

I'm inclined to leave it for now, and let it get fixed as a side effect of separating evaluation and unwinding.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

erlend-aasland reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Python/instrumentation.c
};

static inline bool
opcode_has_event(int opcode) {

@erlend-aasland erlend-aasland Apr 6, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
opcode_has_event(int opcode) {
opcode_has_event(uint8_t opcode)
{

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/instrumentation.c
}

static inline bool
is_instrumented(int opcode) {

@erlend-aasland erlend-aasland Apr 6, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
is_instrumented(int opcode) {
is_instrumented(uint8_t opcode)
{

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/instrumentation.c
assert(test); \
} while (0)

bool valid_opcode(int opcode) {

@erlend-aasland erlend-aasland Apr 6, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
bool valid_opcode(int opcode) {
bool valid_opcode(int opcode)
{

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/instrumentation.c
Comment on lines +999 to +1000
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *target
) {

@erlend-aasland erlend-aasland Apr 12, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *target
) {
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *target)
{

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/instrumentation.c

#define C_RETURN_EVENTS \
((1 << PY_MONITORING_EVENT_C_RETURN) | \
(1 << PY_MONITORING_EVENT_C_RAISE))

@erlend-aasland erlend-aasland Apr 12, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
(1 << PY_MONITORING_EVENT_C_RAISE))
(1 << PY_MONITORING_EVENT_C_RAISE))

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/instrumentation.c
Comment on lines +1558 to +1559
interp->monitoring_tool_names[tool_id] == NULL
) {

@erlend-aasland erlend-aasland Apr 12, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
interp->monitoring_tool_names[tool_id] == NULL
) {
interp->monitoring_tool_names[tool_id] == NULL)
{

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/instrumentation.c
Comment on lines +869 to +871
else {
return MOST_SIGNIFICANT_BITS[bits];
}

@erlend-aasland erlend-aasland Apr 12, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
else {
return MOST_SIGNIFICANT_BITS[bits];
}
return MOST_SIGNIFICANT_BITS[bits];

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/instrumentation.c

/* Should use instruction metadata for this */
static bool
is_super_instruction(int opcode) {

@erlend-aasland erlend-aasland Apr 12, 2023

Copy link
Copy Markdown
Contributor

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.

Suggested change
is_super_instruction(int opcode) {
is_super_instruction(uint8_t opcode)
{

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@fabioz

fabioz commented Apr 12, 2023

Copy link
Copy Markdown
Contributor

I think that is the current behavior, is it not?

If you restart tracing, then you want a line event for the current line. At least, pdb seems to want that. If I don't clear the "last traced line", when setting f_trace, then the pdb tests fail.

TBH, it is all an undocumented black box, so some guess work is required. It seems the best we can do is add test cases, so if you have any more tests I'd be grateful.

Ok, I'll try another round of the debugger tests to see if there's more breakage -- that previous issue didn't really let me get further, so, I'll check and report back -- with bugs in the tracker if that's the case I guess ;)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@fabioz

fabioz commented Apr 12, 2023

Copy link
Copy Markdown
Contributor

@markshannon I just checked and changing the tracing shouldn't duplicate line events in the new tracer (it should only report when the line actually changes).

I created #103471 with a test case for this (which works in Python 3.11 and fails in the current master).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon
markshannon deleted the pep-669 branch April 12, 2023 12:49
@bedevere-bot

bedevere-bot commented Apr 12, 2023

Copy link
Copy Markdown

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot AMD64 Arch Linux TraceRefs 3.x has failed when building commit 411b169.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/all/#builders/484/builds/3091) and take a look at the build logs.
  4. Check if the failure is related to this commit (411b169) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/all/#builders/484/builds/3091

Summary of the results of the build (if available):

Click to see traceback logs
Note: switching to '411b1692811b2ecac59cb0df0f920861c7cf179a'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 411b169281 GH-103082: Implementation of PEP 669: Low Impact Monitoring for CPython (GH-103083)
Switched to and reset branch 'main'

In file included from Python/instrumentation.c:9:
./Include/internal/pycore_object.h:20:35: warning: initialization of ‘PyObject *’ {aka ‘struct _object *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
   20 | #define _PyObject_IMMORTAL_REFCNT 999999999
      |                                   ^~~~~~~~~
Python/instrumentation.c:19:5: note: in expansion of macro ‘_PyObject_IMMORTAL_REFCNT’
   19 |     _PyObject_IMMORTAL_REFCNT,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
./Include/internal/pycore_object.h:20:35: note: (near initialization for ‘DISABLE._ob_next’)
   20 | #define _PyObject_IMMORTAL_REFCNT 999999999
      |                                   ^~~~~~~~~
Python/instrumentation.c:19:5: note: in expansion of macro ‘_PyObject_IMMORTAL_REFCNT’
   19 |     _PyObject_IMMORTAL_REFCNT,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
Python/instrumentation.c:20:5: warning: initialization of ‘PyObject *’ {aka ‘struct _object *’} from incompatible pointer type ‘PyTypeObject *’ {aka ‘struct _typeobject *’} [-Wincompatible-pointer-types]
   20 |     &PyBaseObject_Type
      |     ^
Python/instrumentation.c:20:5: note: (near initialization for ‘DISABLE._ob_prev’)
./Include/internal/pycore_object.h:20:35: warning: initialization of ‘PyObject *’ {aka ‘struct _object *’} from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
   20 | #define _PyObject_IMMORTAL_REFCNT 999999999
      |                                   ^~~~~~~~~
Python/instrumentation.c:25:5: note: in expansion of macro ‘_PyObject_IMMORTAL_REFCNT’
   25 |     _PyObject_IMMORTAL_REFCNT,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
./Include/internal/pycore_object.h:20:35: note: (near initialization for ‘_PyInstrumentation_MISSING._ob_next’)
   20 | #define _PyObject_IMMORTAL_REFCNT 999999999
      |                                   ^~~~~~~~~
Python/instrumentation.c:25:5: note: in expansion of macro ‘_PyObject_IMMORTAL_REFCNT’
   25 |     _PyObject_IMMORTAL_REFCNT,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~
Python/instrumentation.c:26:5: warning: initialization of ‘PyObject *’ {aka ‘struct _object *’} from incompatible pointer type ‘PyTypeObject *’ {aka ‘struct _typeobject *’} [-Wincompatible-pointer-types]
   26 |     &PyBaseObject_Type
      |     ^
Python/instrumentation.c:26:5: note: (near initialization for ‘_PyInstrumentation_MISSING._ob_prev’)
Modules/gcmodule.c:450: visit_decref: Assertion "!_PyObject_IsFreed(op)" failed
Enable tracemalloc to get the memory block allocation traceback

object address  : 0x7f159fa02740
object refcount : 1
object type     : 0x563902ad8980
object type name: dict
object repr     : make: *** [Makefile:1282: Python/frozen_modules/abc.h] Segmentation fault (core dumped)

find: ‘build’: No such file or directory
find: ‘build’: No such file or directory
find: ‘build’: No such file or directory
find: ‘build’: No such file or directory
make: [Makefile:2676: clean-retain-profile] Error 1 (ignored)
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@tacaswell tacaswell mentioned this pull request Apr 13, 2023
Fix #323: Support Python 3.12 python-greenlet/greenlet#327
Merged
aisk pushed a commit to aisk/cpython that referenced this pull request Apr 18, 2023
@markshannon @aisk
pythonGH-103082: Implementation of PEP 669: Low Impact Monitoring for…
989fa92
… CPython (pythonGH-103083)

* The majority of the monitoring code is in instrumentation.c

* The new instrumentation bytecodes are in bytecodes.c

* legacy_tracing.c adapts the new API to the old sys.setrace and sys.setprofile APIs
@arhadthedev arhadthedev mentioned this pull request Apr 19, 2023
gh-103082: Fix shifted field initialization in instrumentation.c #103561
Merged
@furkanonder furkanonder mentioned this pull request May 7, 2023
Missing DTrace probes #104280
Open
scoder added a commit to cython/cython that referenced this pull request May 29, 2023
@scoder
Avoid using the thread state attribute "use_tracing" in Python 3.12 w…
03c498d
…here it was removed from the struct.

See PEP-669 (https://peps.python.org/pep-0669/) and the implementation in python/cpython#103083.
There is more to be done to properly support PEP-669, but this makes it compile.

See #5450
@scoder scoder mentioned this pull request Jun 6, 2023
[BUG] Profiling/tracing fails in Py3.12 cython/cython#5470
Closed
@gvanrossum gvanrossum mentioned this pull request Aug 8, 2023
Bytecode documentation issues for Python 3.12 #107457
Closed
20 tasks
@AlexWaygood AlexWaygood mentioned this pull request Oct 16, 2023
Stubtest: add some hacks so that sys.monitoring is checked in typeshed's CI python/mypy#16269
Open
@nedbat nedbat mentioned this pull request Nov 29, 2023
Add sys.monitoring from Python 3.12 python/typeshed#10890
Merged
Zheaoli added a commit to Zheaoli/cpython that referenced this pull request Oct 6, 2024
@Zheaoli
Fix function__return and function__entry dTrace probe missing aft…
be76c1f
…er `pythonGH-103083`
@Zheaoli Zheaoli mentioned this pull request Oct 6, 2024
GH-98894: Fix function__return and function__entry dTrace probe missing after GH-103083 #125019
Closed
Zheaoli added a commit to Zheaoli/cpython that referenced this pull request Oct 7, 2024
@Zheaoli
Fix function__return and function__entry dTrace probe missing aft…
f11b7c4
…er `pythonGH-103083`
@pablogsal pablogsal mentioned this pull request Dec 8, 2025
gh-98894: Restore function entry/exit DTrace probes #142397
Merged
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@iritkatriel iritkatriel iritkatriel left review comments
@erlend-aasland erlend-aasland erlend-aasland left review comments
@kumaraditya303 kumaraditya303 kumaraditya303 left review comments
@gvanrossum gvanrossum gvanrossum approved these changes
+2 more reviewers
@fabioz fabioz fabioz left review comments
@artemmukhin artemmukhin artemmukhin left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

12 participants

@markshannon @bedevere-bot @drdavella @gvanrossum @nedbat @fabioz @gaogaotiantian @brandtbucher @erlend-aasland @iritkatriel @artemmukhin @kumaraditya303
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.