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

Skip to content

Conversation

@KirillOsenkov
Copy link
Contributor

If we have two assemblies with type forwarders that point to each other, we enter an infinite loop and a stack overflow.

This breaks the cycle by detecting reentrancy.

Fixes #706

If we have two assemblies with type forwarders that point to each other, we enter an infinite loop and a stack overflow.

This breaks the cycle by detecting reentrancy.

Fixes jbevain#706
@KirillOsenkov KirillOsenkov force-pushed the dev/kirillo/reentrancy branch from 655d831 to 5ad5672 Compare October 15, 2023 22:40
@KirillOsenkov
Copy link
Contributor Author

@jbevain I even added a test!

@jonlouie
Copy link

@jbevain What can community contributors do to get this PR merged and released? This resolves a bug that my team encountered.

@KirillOsenkov
Copy link
Contributor Author

FYI @jbevain adding the boolean field is free because of alignment padding:

image

@KirillOsenkov
Copy link
Contributor Author

KirillOsenkov commented Nov 20, 2024

Unfortunately it's not free for 32-bit (the size goes from 32 to 36 bytes. But there are not many exported types per assembly, so it's honestly negligible.

Type layout for 'ExportedType'
Size: 36 bytes. Paddings: 3 bytes (%8 of empty space)
|==============================================|
| Object Header (4 bytes)                      |
|----------------------------------------------|
| Method Table Ptr (4 bytes)                   |
|==============================================|
|   0-3: String namespace (4 bytes)            |
|----------------------------------------------|
|   4-7: String name (4 bytes)                 |
|----------------------------------------------|
|  8-11: IMetadataScope scope (4 bytes)        |
|----------------------------------------------|
| 12-15: ModuleDefinition module (4 bytes)     |
|----------------------------------------------|
| 16-19: ExportedType declaring_type (4 bytes) |
|----------------------------------------------|
| 20-23: UInt32 attributes (4 bytes)           |
|----------------------------------------------|
| 24-27: Int32 identifier (4 bytes)            |
|----------------------------------------------|
|    28: Boolean reentrancyGuard (1 byte)      |
|----------------------------------------------|
| 29-31: padding (3 bytes)                     |
|----------------------------------------------|
| 32-35: MetadataToken token (4 bytes)         |
| |===============================|            |
| |   0-3: UInt32 token (4 bytes) |            |
| |===============================|            |
|==============================================|

@cwensley
Copy link

We've been running into these same stack overflow issues quite a bit but intermittently and not related to circular type forwarders (from what I can tell). I have confirmed this fixes our issues as well, so it'd be nice to have it included in an official release.

@jbevain
Copy link
Owner

jbevain commented Feb 5, 2025

@KirillOsenkov just move the field with the other fields and this is good to go :)

@KirillOsenkov
Copy link
Contributor Author

It's happening.gif

@jbevain jbevain merged commit f1c428a into jbevain:master Mar 5, 2025
2 checks passed
@KirillOsenkov KirillOsenkov deleted the dev/kirillo/reentrancy branch March 5, 2025 19:30
KirillOsenkov added a commit to KirillOsenkov/MetadataTools that referenced this pull request Apr 17, 2025
It is not thread-safe. Cecil resolver can't be called concurrently from multiple threads because of jbevain/cecil#806. By sharing the assembly cache we get intermittent Circularity exceptions.
@mrvoorhe
Copy link
Contributor

mrvoorhe commented May 5, 2025

@jbevain @KirillOsenkov This change is breaking our multi-threaded immediate read logic. Is this change worth it? Cecil is generally a bit delicate when it comes to multi-thread but we've navigated successfully for years. I could be mistaken, but I believe this change introduces a new gotcha pattern that didn't exist previously.

@KirillOsenkov
Copy link
Contributor Author

It is an unfortunate side effect that I've also discovered only recently, after the PR was merged.

The tradeoff here is the following: people used to run into stack overflows all the time without this check, and there's nothing you can do as a consumer to defend against that.

However you do have control over calling Resolve, so it's straightforward to just put a lock around that if you insist that you need parallelism.

We could also put a lock directly into the resolver to formalize the expectation that the reader is thread-safe. However the consumer could do that in the rare chance they need to.

This is my opinion only.

@mrvoorhe
Copy link
Contributor

mrvoorhe commented May 6, 2025

It is an unfortunate side effect that I've also discovered only recently, after the PR was merged.

The tradeoff here is the following: people used to run into stack overflows all the time without this check, and there's nothing you can do as a consumer to defend against that.

However you do have control over calling Resolve, so it's straightforward to just put a lock around that if you insist that you need parallelism.

We could also put a lock directly into the resolver to formalize the expectation that the reader is thread-safe. However the consumer could do that in the rare chance they need to.

This is my opinion only.

I can live with doing nothing if this change is preferred. I was able to duplicate the Resolve logic into our code and avoid calling ExportedType.Resolve without too much trouble.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Defend against cyclical Type Forwarders?

5 participants