Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --What happened: I wrote a test creating multiple isomorphic Events with an "". and stumbled upon a data race. The data race happens in the function. It seems that the Event Series field can be read and written at the same time by two different goroutines. . This seems to happen in only one scenario: (1) a singleton Event is created (2) an isomorphic Event is created (1) lock addition to cache (2) wait for the lock to be released (1) unlock (2) lock + the Series (1) the freshly created Series (1) singleton Event is now considered as a series That being said, this seems to lead to some data inconsistencies as the singleton Event would be created with an unexpected Series. <details<summaryData race</summary|================== WARNING: DATA RACE Write at 0x00c0001237a0 by goroutine 23: (eventBroadcasterImpl).recordToSink.func1.1() +0x536 (eventBroadcasterImpl).recordToSink.func1() +0x84 Previous read at 0x00c0001237a0 by goroutine 24: (Event).DeepCopyInto() +0x64 (Event).DeepCopy() +0x56 (Event).DeepCopyObject() +0x33 () +0xe8 (CreateActionImpl).DeepCopy() <autogenerated:1 +0xb7 (Fake).Invokes() +0x103 (FakeEvents).Create() +0x214 (EventSinkImpl).Create() +0x195 () +0x32c (eventBroadcasterImpl).attemptRecording() +0xe4 (eventBroadcasterImpl).recordToSink.func1() +0x9d Goroutine 23 (running) created at: (eventBroadcasterImpl).recordToSink() +0x159 (eventBroadcasterImpl).startRecordingEvents.func1() +0x75 (eventBroadcasterImpl).StartEventWatcher.func2() +0x91 Goroutine 24 (running) created at: (eventBroadcasterImpl).recordToSink() +0x159 (eventBroadcasterImpl).startRecordingEvents.func1() +0x75 (eventBroadcasterImpl).StartEventWatcher.func2() +0x91 |================== |================== WARNING: DATA RACE Read at 0x00c0001236f0 by goroutine 23: (Event).DeepCopyInto() +0x64 (Event).DeepCopy() +0x72 () +0x4d () +0x98 (eventBroadcasterImpl).attemptRecording() +0xe4 (eventBroadcasterImpl).recordToSink.func1() +0x9d Previous write at 0x00c0001236f0 by goroutine 24: () +0x2e4 (eventBroadcasterImpl).attemptRecording() +0xe4 (eventBroadcasterImpl).recordToSink.func1() +0x9d Goroutine 23 (running) created at: (eventBroadcasterImpl).recordToSink() +0x159 (eventBroadcasterImpl).startRecordingEvents.func1() +0x75 (eventBroadcasterImpl).StartEventWatcher.func2() +0x91 Goroutine 24 (running) created at: (eventBroadcasterImpl).recordToSink() +0x159 (eventBroadcasterImpl).startRecordingEvents.func1() +0x75 (*eventBroadcasterImpl).StartEventWatcher.func2() +0x91 |================== </detailsWhat you expected to happen: No data race. How to reproduce it (as minimally and precisely as possible): You can run the following code snippet alongside any data race detector. I personally used . Anything else we need to know?: Environment: v0.25.0
/sig api-machinery
/assign Would you mind taking a look to this one? thanks!
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
Stale issues rot after 30d of inactivity. Mark the issue as fresh with . Rotten issues close after an additional 30d of inactivity. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle rotten
/remove-lifecycle rotten
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
Stale issues rot after 30d of inactivity. Mark the issue as fresh with . Rotten issues close after an additional 30d of inactivity. If this issue is safe to close now please do so with . Send feedback to sig-contributor-experience at . /lifecycle rotten
Rotten issues close after 30d of inactivity. Reopen the issue with . Mark the issue as fresh with . Send feedback to sig-contributor-experience at . /close
Closing this issue. <detailsIn response to : Rotten issues close after 30d of inactivity. Reopen the issue with . Mark the issue as fresh with . /close Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
We might also want to look into that again for /reopen /assign /unassign
/remove-lifecycle rotten
Reopened this issue. <detailsIn response to : We might also want to look into that again for /unassign Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
/cc
/cc
I was able to reproduce this issue with client-go v0.25.0. I updated the reproducer to be compatible with the latest version of the client and attached the data race trace.
are you gong to submit a fix for this one? thanks. /triage accepted
Yes, I am planning to submit a fix for this issue.
The Kubernetes project currently lacks enough contributors to adequately respond to all issues and PRs. This bot triages issues and PRs according to the following rules: After 90d of inactivity, is applied After 30d of inactivity since was applied, is applied After 30d of inactivity since was applied, the issue is closed You can: Mark this issue or PR as fresh with Mark this issue or PR as rotten with Close this issue or PR with Offer to help out with [Issue Triage][1] Please send feedback to sig-contributor-experience at . /lifecycle stale [1]:
A PR is out to fix this issue: /remove-lifecycle stale

Discussion:
Count:            1, LastObservedTime: metav1.MicroTime{Time: clock.Now()}, } <del> return isomorphicEvent </del> <ins> // Make a copy of the Event to make sure that recording it // doesn't mess with the object stored in cache. return isomorphicEvent.DeepCopy() </ins> } e.eventCache[eventKey] = eventCopy <del> return eventCopy </del> <ins> // Make a copy of the Event to make sure that recording it doesn't // mess with the object stored in cache. return eventCopy.DeepCopy() </ins> }() if evToRecord != nil { <del> recordedEvent := e.attemptRecording(evToRecord) if recordedEvent != nil { recordedEventKey := getKey(recordedEvent) e.mu.Lock() defer e.mu.Unlock() e.eventCache[recordedEventKey] = recordedEvent } </del> <ins> // TODO: Add a metric counting the number of recording attempts e.attemptRecording(evToRecord) // We don't want the new recorded Event to be reflected in the // client's cache because server-side mutations could mess with the // aggregation mechanism used by the client. </ins> } }() }

package events import ( <ins> "context" </ins> "strconv" "testing" "time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" <ins> fake "k8s.io/client-go/kubernetes/fake" </ins> "k8s.io/client-go/kubernetes/scheme" restclient "k8s.io/client-go/rest" ref "k8s.io/client-go/tools/reference"

close(stopCh) } <ins> // TestEventSeriesWithEventSinkImplRace verifies that when Events are emitted to // an EventSink consecutively there is no data race.  This test is meant to be // run with the `-race` option. func TestEventSeriesWithEventSinkImplRace(t *testing.T) { kubeClient := fake.NewSimpleClientset() eventSink := &EventSinkImpl{Interface: kubeClient.EventsV1()} eventBroadcaster := NewBroadcaster(eventSink) stopCh := make(chan struct{}) eventBroadcaster.StartRecordingToSink(stopCh) recorder := eventBroadcaster.NewRecorder(scheme.Scheme, "test") recorder.Eventf(&v1.ObjectReference{}, nil, v1.EventTypeNormal, "reason", "action", "", "") recorder.Eventf(&v1.ObjectReference{}, nil, v1.EventTypeNormal, "reason", "action", "", "") err := wait.PollImmediate(100*time.Millisecond, 5*time.Second, func() (done bool, err error) { events, err := kubeClient.EventsV1().Events(metav1.NamespaceDefault).List(context.TODO(), metav1.ListOptions{}) if err != nil { return false, err } if len(events.Items) != 1 { return false, nil } if events.Items[0].Series == nil { return false, nil } return true, nil }) if err != nil { t.Fatal("expected that 2 identical Eventf calls would result in the creation of an Event with a Serie") } } </ins> func validateEvent(messagePrefix string, expectedUpdate bool, actualEvent *eventsv1.Event, expectedEvent *eventsv1.Event, t *testing.T) { recvEvent := *actualEvent