New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
$using
variable should support method call on it
#10876
Comments
Related: #10499 Depending on exactly what's preventing the current use of methods with $using: (is it a deliberate constraint applied manually? Plumbing not entirely hooked up?) We may want to implement this alongside changing $using in threadjob/foreach -parallel to $ref: That is, if the committee agrees with that change when it is reviewed 😄 Edit: ah, I see it has been. Nevermind then :) |
There are places where
especially as This would be a good change but it should be made clear somewhere what works and what does not. This fails because |
Honestly given the current strictures of $using, I would think in makes more sense for #10499 to be reconsidered and a different prefix chosen, both to avoid complicating the code paths in the parser for $using (how is the parser meant to differentiate $using restrictions in the different scenarios? That makes little sense to me), and to have it be clear to users. If they get a parse error for applying indexing or property access to $using now, I don't think making that parse error somehow conditional based on what command it's being passed to is a good user experience at all. What about creating and storing scriptblocks ahead of time? There's no meaningful way to make the distinction in the parser. Different prefixes kind of have to be used here, or we have to offload the error states to the individual commands, which leaves us much more open to even more variant, confusing, and inconsistent behaviours. Not great UX at all in my opinion. |
I have struggled to get my head round this, but this may help others. What we have here is the parser blocking those dangerous operations - where they are can be seen.
However one can hide the unsafe behaviour
This doesn't work for simple types like strings and integers. So we have two requests, one here which says make it easier to do things which are not thread-safe, and #10499 - I'm not sure if it is saying block non-thread safe things completely or ensure that people can't do something unsafe by accident - the parser is doing the latter. |
@jhoneill the latter parts of the discussion in #10499 make it more clear that a
There will always be users who don't fully comprehend what they're doing; I think the onus is on us to make it clear that these two usages are different in a pretty important way, and whether users seek to understand it before using it is then up to them. Hiding the difference by simply modifying the existing behaviour of |
The quickest workaround for calling methods is to enclose the reference in PS> $ht = @{ one=1 }; % -parallel { ($using:ht).Add('two', 2) }; $ht
Name Value
---- -----
one 1
two 2 I think the confusion stems from thinking of PowerShell - unlike Bash, for instance - doesn't normally make that distinction: in
Whether the value These are the same by-[reference]-value passing semantics as when you call a script or function (except that nothing prevents you from assigning to a parameter variable later): if the parameter value is a value-type instance, you get a copy, if it's a reference-type instance, you get a copy of the reference - and therefore the potential for modifying the referenced object. Therefore, I don't think I can't think of a prefix that could capture the complexity of the above. However, we do have to properly document the issue. And, back to @daxian-dbw's original issue, there's no reason not to allow method calls directly on PS> $ht = @{ one=1 }; % -parallel { $using:ht.count }
1 |
@mklement0 😕 |
These terms are not mutually exclusive:
This applies to any function/script call in PowerShell - even though the syntax is different here, the concept is the same: # Same example as above, only with a local script-block call:
PS> $ht = @{ one=1 }; & { param($htParam) $htParam.Add('two', 2) } $ht; $ht
Name Value
---- -----
one 1
two 2 Again, this is unrelated to Yes, in the context of That challenge is already documented in the Despite the context-specific differences, in all these contexts |
That's not what happens in Quick demo
This works
Can we change the contents ? And does that change the content of source variable. ?
The "remote" machine can't write back to the original - it's Call-by-value behavior.
We got an error because one thread job sees the modification made by the other.
The value in the callers scope has changed. This is call-by-reference behavior. @PaulHigin explained it in post here.
That's what we see here : the parallel runspaces have read/write access to a variable in the calling runspace. There might be some implementation oddity which means it is not truly call-by-reference, but using the "if it walks like a duck and quacks like a duck then just call it a duck" logic, that's how I'd describe it. @vexx32 Agree. I think as things are, there is a bit of "jumping through hoops" with using: as it is, that means you don't accidentally get unsafe behaviour. I keep meaning to look at synchronized hash tables and other things which designed for thread safety
|
None of what you state contradicts my explanation, except for fuzzy terminology that may explain why you think there is a disagreement (see below). Yes, there is a thread-safety issue that is unique to To recap my argument:
As for your examples:
Here's where it gets fuzzy: It isn't the content of the source variable that is changed, at least not in the sense that a different value is now stored in it - the latter is impossible in this scenario. What changes is the object that the unchanged variable-content references.
As argued before, it is by-reference behavior in the same sense as passing any reference-type instance to a script/function passes a copy of the reference. This is fundamental PowerShell / .NET behavior that is to be expected, and needs to be understood generally, in all contexts where you pass parameters in-process. To recap my previous example:
Technically, this is still passing by value in that a copy of the
So, in effect, what is technically by-value passing is situationally "object-pointer passing" - as in C/C++, except that the fact is more obvious there. The type of by-value semantics that you have in mind - always passing an independent copy of the data - are de facto impossible to achieve.
|
All this back and forth only seems to perfectly illustrate the exact reason that we should separate the two types of Confusion and lengthy discussions are already present, and a few notes in documentation are unlikely to be sufficient to properly illustrate the differences, in my view. 🙂 As you say, truly deep-cloning is impossible. By and large, most $using usage is currently working with/around the serialization and job interfaces. In comparison, the way it behaves in Start-ThreadJob or ForEach-Object -Parallel is going to be very different, even if on a technicality it's sort of the same kind of thing. 🙂 |
My conclusion is different:
Pragmatically speaking, the issue only arises:
It is only then that you need to be aware of the thread-safety issue, and an explanation of the issue in the I suppose we should also document the complementary solution: wanting to work with thread-local clones of # OK, because thread-local clones of the hashtable are being modified.
PS> $ht = @{one=1}; 1..2 | % -parallel { $ht = ($using:ht).Clone(); $ht.Add('two', 2); $ht }
Name Value
---- -----
one 1
two 2
one 1
two 2
|
🤔 Fair points. I dislike the confusion that is more or less inevitable here, but you're right in that Appreciate you taking the time to lay it all out! 💖 |
I'll let you argue with @BrucePay , see #10499 (comment)
I understand what you are saying; after executing
|
They behave the same as in the parameter-passing / thread-based $ht = @{ one = 1 }; & { $ht.one = 2 }; $ht
Name Value
---- -----
one 2
Also note that users who are accustomed to It is only if they happen to pass Again: A different namespace prefix is incapable of conveying these subtle differences while obscuring the commonalities and increasing developers' memory burden. |
@jhoneill: As for the quote from #10499 (comment): That PowerShell uses boxing (wrapping value-type instances in a reference-type instance) behind the scenes is irrelevant to the effective behavior in PowerShell code: PS> $i = 1; & { param($intParam) ++$intParam } $i; $i
1 # The value-type instance stored in the caller's $i variable was NOT modified. Even in C#, where you can make boxing explicit, boxing doesn't change the semantics of parameters that are ultimately value-type instances (you can paste the code directly at a > class Foo { public static void Bar(object i) { i = 2; } }; object i = 1; Foo.Bar(i); i
1 // The value-type instance stored in the caller's i variable was NOT modified. Even though local |
Look, you can argue with Bruce if you want to :-) The OP talks about it as by reference, and @PaulHigin 's post talks about parallel being different from other using, being by reference.
then
I thought we had established using with parallel can change the variable in the calling scope (unlike remoting) but method calls don't work unless the passed variable is wrapped in () unlike a script block -from the initial post.
i.e he doesn't want to write
Sorry I was using it in the sense of "excessively work"
This is what was being said in #10499 . And you say users shouldn't be able to modify values in the caller's scope.. Most people would say after this : ensures the last thread to try to change $ht.value is random.
|
Yes. Everything that needs to be said about this has been said.
The point of the example was that you can't modify value-type instances in this scenario - whether or not they have mutating methods (which is rare to begin with): # Define a value type with a mutating method.
Add-Type 'public struct Foo { public int Value { get; private set; } public void Increment() { ++Value; } }'
$foo = [Foo]::new()
$foo.Increment() # $foo.Value is now 1
# Passing the value type as a parameter value passes a *data copy* to the
# script block, so any mutating methods invoked on that copy only affect *that copy*.
& { param($fooParam) $fooParam.Increment() } $foo
$foo.Value # still 1
Yes. And I agree that
I'm confused:
I didn't say that, and I didn't mean to (though I certainly think it should be a deliberate action). What I did say was that modifying objects in the caller's scope is simply not an option in the cross-process scenarios, so the in-process, cross-thread scenario of being able to modify the very same object is simply not an issue.
Indeed, as has been amply discussed - including the scenario where that can be used as a feature. |
OK.... We have two competing requests. This one which says accessing a method of a
Right; we all know what can be observed with PowerShell as it is now. And I don't think I'm misrepresenting anything to say in a "Make it easier" vs "Already dangerously easy" debate you and the OP in this issue would be inclined towards the former. I'm not strongly for or against either of them. Nor am I convinced that the label should be changed in an attempt to satisfy both. I think the present position is OK, but in an uncommitted and agnostic way :-)
What I tried to say was this; making a script author write
I may have misunderstood / lost the context in quoting what you said 3 paragraphs from the bottom of #10876 (comment)
Because I lost track of what you were saying should or should not be possible. Can we park that since I we agree that it is indeed a feature ? The question is
I'm inclined towards 3 which is the status quo; nothing said for either of the others (so far) has convinced me of their case - in my view both have equal and opposite merit. |
I see this issue and #10499 as unrelated:
Re 3.: I generally think it's not a good idea to encumber the syntax in order to prevent pitfalls (see #6551, for instance); it is of necessity arbitrary and therefore:
While it ultimately doesn't matter, I suspect that the current inability to call methods directly on
The only required barrier here is one of necessity: attempts to assign directly to a Current, general error message:
In the case of attempting to assign to a
|
@mklement0
The parser error is stopping me from trying to call a method which won't exist (and wouldn't make sense).
|
Glad to hear it, @jhoneill. Yes, only a handful of known types deserialize with type fidelity in remoting / background jobs; all others are emulated with method-less (#10916 seems to want to change that selectively, which sounds quite challenging.) As an aside: While that fact is specified in the MS-PRSP protocol specification, there's virtually no information in the end-user documentation ( For all the reasons previously discussed, my preference is not to erect syntax barriers, especially not selectively - documentation should suffice.
As for how likely it is that users will encounter the pitfall: Since methods are often lost in cross-process parallelism due to deserialization, there may not even be an expectation of being able to call methods - anecdotally, the vast majority of remoting / background-job commands I've seen on Stack Overflow simply pass values that are accessed as-is (no method calls, rarely property access). So that means that only the following users are affected: Those who actively use not only methods on deserialized objects, but specifically mutating methods and who expect such mutations to be confined to an independent copy of the caller's value. Finally, let me offer a way through the conceptual fog of by-value vs. by-reference argument passing vs. data that happens to be a reference:
The two concepts are independent of one another, but it gets confusing, because something passed by-value (in the data-holder sense) can still be a reference (in the data sense) that the callee can potentially modify. Again, this behavior is fundamental to all in-process parameter passing. |
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you. |
1 similar comment
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you. |
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you. |
This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes. |
$using:
prefixed variables are used inForeach-Object -Parallel
andStart-ThreadJob
to pass value of reference types. Given the value is passed by reference, it's natural to call method on it directly in a thread job or script running in another Runspace, and thus we should remove the semantics check and allow expression to be used on the$using:
variable.Steps to reproduce
{ $using:blah.Add() }
Expected behavior
No error thrown.
Actual behavior
Environment data
The text was updated successfully, but these errors were encountered: