Skip to content
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

SseEmitter: connection closed after first event #25987

Closed
reta opened this issue Oct 28, 2020 · 5 comments
Closed

SseEmitter: connection closed after first event #25987

reta opened this issue Oct 28, 2020 · 5 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Milestone

Comments

@reta
Copy link

reta commented Oct 28, 2020

It seems like there is a regression introduced into SseEmitter in latest 5.2.10.RELEASE (apparently #25442), it now returns to the client only first SSE event.

How to reproduce

package com.example.sse.emmiter;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter.SseEventBuilder;

@SpringBootApplication
public class SseEmitterRegressionApplication {
    @RestController
    @EnableAutoConfiguration
    static class LibraryController {
        @GetMapping("/sse")
        public SseEmitter streamSseMvc() {
            final SseEmitter emitter = new SseEmitter();
            final ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor();
            
            sseMvcExecutor.execute(() -> {
                try {
                    for (int eventId = 1; eventId <= 5; ++eventId) {
                        SseEventBuilder event = SseEmitter.event()
                            .id(Integer.toString(eventId))
                            .data(new Book("New Book #" + eventId, "Author #" + eventId), MediaType.APPLICATION_JSON)
                            .name("book");
                        emitter.send(event);
                        Thread.sleep(100);
                    }
                    emitter.complete();
                } catch (Exception ex) {
                    emitter.completeWithError(ex);
                }
            });
            
            return emitter;
        }
    }
    
    static class Book {
        private String title;
        private String author;

        public Book() {
        }

        public Book(final String title, final String author) {
            this.setTitle(title);
            this.setAuthor(author);
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getAuthor() {
            return author;
        }

        public void setAuthor(String author) {
            this.author = author;
        }
        
        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }

        @Override
        public boolean equals(Object obj) {
            return EqualsBuilder.reflectionEquals(this, obj);
        }
        
        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(SseEmitterRegressionApplication.class, args);
    }
}
  • using latest Spring Boot 2.3.4.RELEASE and Spring Framework 5.2.9.RELEASE
$ curl http://localhost:8080/sse -iv

> GET /sse HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.71.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: text/event-stream
< Transfer-Encoding: chunked
< Date: Wed, 28 Oct 2020 22:09:00 GMT
<
{ [15 bytes data]
100   335    0   335    0     0    589      0 --:--:-- --:--:-- --:--:--   590HTTP/1.1 200

Content-Type: text/event-stream
Transfer-Encoding: chunked
Date: Wed, 28 Oct 2020 22:09:00 GMT

id:1
data:{"title":"New Book #1","author":"Author #1"}
event:book

id:2
data:{"title":"New Book #2","author":"Author #2"}
event:book

id:3
data:{"title":"New Book #3","author":"Author #3"}
event:book

id:4
data:{"title":"New Book #4","author":"Author #4"}
event:book

id:5
data:{"title":"New Book #5","author":"Author #5"}
event:book
  • using latest Spring Boot 2.3.4.RELEASE and Spring Framework 5.2.10.RELEASE (overriding with <spring-framework.version>5.2.10.RELEASE</spring-framework.version>)
$ curl http://localhost:8080/sse -iv

> GET /sse HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.71.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: text/event-stream
< Transfer-Encoding: chunked
< Date: Wed, 28 Oct 2020 22:10:33 GMT
<
{ [15 bytes data]
100    54    0    54    0     0   2250      0 --:--:-- --:--:-- --:--:--  2250HTTP/1.1 200
Content-Type: text/event-stream
Transfer-Encoding: chunked
Date: Wed, 28 Oct 2020 22:10:33 GMT

id:1
data:{"title":"New Book #1","author":"Author #1"}

Reproducible all the time. Please advice if this is a regression or SseEmitter semantics has changed (would appreciate documentation pointers) or more details are needed, thank you.

@reta reta changed the title SseEmitter: regressing in 5.2.10.RELEASE SseEmitter: regression in 5.2.10.RELEASE Oct 28, 2020
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 28, 2020
@rstoyanchev rstoyanchev self-assigned this Oct 29, 2020
@rstoyanchev rstoyanchev added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Oct 29, 2020
@rstoyanchev
Copy link
Contributor

The regression is due to optimizations in Jackson codecs and converters, issue #25910.

@rstoyanchev rstoyanchev added type: regression A bug that is also a regression and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Oct 29, 2020
@rstoyanchev rstoyanchev added this to the 5.3.1 milestone Oct 29, 2020
@rstoyanchev rstoyanchev changed the title SseEmitter: regression in 5.2.10.RELEASE SseEmitter: connection closed after first event Oct 29, 2020
@rstoyanchev rstoyanchev added the for: backport-to-5.2.x Marks an issue as a candidate for backport to 5.2.x label Oct 29, 2020
@spring-projects-issues spring-projects-issues added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-5.2.x Marks an issue as a candidate for backport to 5.2.x labels Oct 29, 2020
@reta
Copy link
Author

reta commented Oct 29, 2020

Thanks a lot for quick update, @rstoyanchev

@christophejan
Copy link
Contributor

It's look that there is the same regression with controller producing multipart. The server response is truncated after any jackson part body (the boundary after this part is missing, all remaining parts are ignored).

@rstoyanchev
Copy link
Contributor

Yes this will impact all cases in Spring MVC where Jackson is used to write but the response needs to remain open.

@rstoyanchev
Copy link
Contributor

This should be fixed now for 5.2.11 and 5.3.1.

In the mean time as a workaround you could disable the JsonGenerator.Feature.AUTO_CLOSE_TARGET on the ObjectMapper. For example in Spring Boot:

@Bean
public Jackson2ObjectMapperBuilderCustomizer om() {
	return builder -> builder.featuresToDisable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants