Skip to content

break in the pipeline: Why does End run in some cases and not in others? #5811

Closed

Description

@alx9r

Steps to reproduce

do { 
    1..10 | 
        % { if ($_ -gt 2) { break }; $_ } | 
        Sort-Object -Descending
 } while ($false)

do {
    % { foreach ( $i in 100..110 ) { $i } } | 
    % { if ($_ -gt 102) { break }; $_ } | 
    Sort-Object -Descending
} while ($false)

Expected behavior

Either no output or

2
1
0
102
101
100

Actual behavior

102
101
100

Environment data

> $PSVersionTable

Name                           Value                                            
----                           -----                                            
PSVersion                      6.0.0-rc.2                                       
PSEdition                      Core                                             
GitCommitId                    v6.0.0-rc.2                                      
OS                             Microsoft Windows 6.3.9600                       
Platform                       Win32NT                                          
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                          
PSRemotingProtocolVersion      2.3                                              
SerializationVersion           1.1.0.1                                          
WSManStackVersion              3.0                                              

Activity

  1. alx9r commented on Jan 7, 2018

    @alx9r
    Author

    This arose while trying to reproduce the examples in #3821.

  2. added
    WG-Enginecore PowerShell engine, interpreter, and runtime
    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassif
    on Jan 8, 2018
  3. mklement0 commented on Jan 8, 2018

    @mklement0
    Contributor

    I don't have an explanation, but the issue is related to using % (ForEach-Object) at the start of the pipeline.

    If you replace the initial % {... } with $(...) - which is the more idiomatic way to start a pipeline with an expression - the problem goes away (yields no output, as expected, because Sort-Object's end block doesn't get to run):

    do {
      $(foreach ( $i in 100..110 ) { $i }) |   # Use $(...) rather than % {...}
      % { if ($_ -gt 102) { break }; $_ } | 
      Sort-Object -Descending
    } while ($false)

    Still, it would be good to have an explanation for your symptom.

  4. alx9r commented on Jan 8, 2018

    @alx9r
    Author

    @izybkr wrote this in #3897 (comment):

    break and continue work dynamically - meaning the break/continue statement searches for an appropriate loop to break from at runtime.
    ...
    Under the covers, the break turns into an exception (always V2 or earlier, V3 onwards if not lexically within a loop statement), the exception is always silent (because you don't really want to think of break as an exception, so it's silent even if we don't find a matching loop.

    By that reasoning, I think

    do { 
        1..10 | 
            % { if ($_ -gt 2) { break }; $_ } | 
            Sort-Object -Descending
     } while ($false)

    outputs nothing because the "exception" thrown by break is not caught until it reaches the do{} which is the first loop encountered. That loop is outside the pipeline, so flow of control continues outside the pipeline and end{} doesn't run. On the other hand I think in

    do {
        % { foreach ( $i in 100..110 ) { $i } } | 
        % { if ($_ -gt 102) { break }; $_ } | 
        Sort-Object -Descending
    } while ($false)

    the first loop encountered by the "exception" thrown by break is foreach{}, so flow of control continues at the closing } of the foreach and the pipeline continues as usual from there.

    Jason Shirk (@lzybkr) Is this right?

  5. lzybkr commented on Jan 8, 2018

    @lzybkr
    Contributor

    alx9r - your understanding is correct.

    Note that you could have used & instead of % in the first stage of the pipeline and gotten the same result - the key is to ensure the foreach loop statement has not completed before hitting the break statement. As Michael Klement (@mklement0) points out, $() would fully execute the loop before writing anything to the pipeline.

  6. alx9r commented on Jan 8, 2018

    @alx9r
    Author

    This all makes sense to me now and seems to match the behavior I'm seeing. Thank you Jason Shirk (@lzybkr).

  7. mklement0 commented on Jan 8, 2018

    @mklement0
    Contributor

    I hadn't even considered the execute-in-full-first aspect - good to know that & { ... } - as opposed to $(...) - can send an expression-based statement's output to the pipeline as it is being produced.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifResolution-AnsweredThe question is answered.WG-Enginecore PowerShell engine, interpreter, and runtime

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions