github.com/streamdal/segmentio-kafka-go@v0.4.47-streamdal/offsetcommit.go (about) 1 package kafka 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "net" 8 "time" 9 10 "github.com/segmentio/kafka-go/protocol/offsetcommit" 11 ) 12 13 // OffsetCommit represent the commit of an offset to a partition. 14 // 15 // The extra metadata is opaque to the kafka protocol, it is intended to hold 16 // information like an identifier for the process that committed the offset, 17 // or the time at which the commit was made. 18 type OffsetCommit struct { 19 Partition int 20 Offset int64 21 Metadata string 22 } 23 24 // OffsetCommitRequest represents a request sent to a kafka broker to commit 25 // offsets for a partition. 26 type OffsetCommitRequest struct { 27 // Address of the kafka broker to send the request to. 28 Addr net.Addr 29 30 // ID of the consumer group to publish the offsets for. 31 GroupID string 32 33 // ID of the consumer group generation. 34 GenerationID int 35 36 // ID of the group member submitting the offsets. 37 MemberID string 38 39 // ID of the group instance. 40 InstanceID string 41 42 // Set of topic partitions to publish the offsets for. 43 // 44 // Not that offset commits need to be submitted to the broker acting as the 45 // group coordinator. This will be automatically resolved by the transport. 46 Topics map[string][]OffsetCommit 47 } 48 49 // OffsetFetchResponse represents a response from a kafka broker to an offset 50 // commit request. 51 type OffsetCommitResponse struct { 52 // The amount of time that the broker throttled the request. 53 Throttle time.Duration 54 55 // Set of topic partitions that the kafka broker has accepted offset commits 56 // for. 57 Topics map[string][]OffsetCommitPartition 58 } 59 60 // OffsetFetchPartition represents the state of a single partition in responses 61 // to committing offsets. 62 type OffsetCommitPartition struct { 63 // ID of the partition. 64 Partition int 65 66 // An error that may have occurred while attempting to publish consumer 67 // group offsets for this partition. 68 // 69 // The error contains both the kafka error code, and an error message 70 // returned by the kafka broker. Programs may use the standard errors.Is 71 // function to test the error against kafka error codes. 72 Error error 73 } 74 75 // OffsetCommit sends an offset commit request to a kafka broker and returns the 76 // response. 77 func (c *Client) OffsetCommit(ctx context.Context, req *OffsetCommitRequest) (*OffsetCommitResponse, error) { 78 now := time.Now().UnixNano() / int64(time.Millisecond) 79 topics := make([]offsetcommit.RequestTopic, 0, len(req.Topics)) 80 81 for topicName, commits := range req.Topics { 82 partitions := make([]offsetcommit.RequestPartition, len(commits)) 83 84 for i, c := range commits { 85 partitions[i] = offsetcommit.RequestPartition{ 86 PartitionIndex: int32(c.Partition), 87 CommittedOffset: c.Offset, 88 CommittedMetadata: c.Metadata, 89 // This field existed in v1 of the OffsetCommit API, setting it 90 // to the current timestamp is probably a safe thing to do, but 91 // it is hard to tell. 92 CommitTimestamp: now, 93 } 94 } 95 96 topics = append(topics, offsetcommit.RequestTopic{ 97 Name: topicName, 98 Partitions: partitions, 99 }) 100 } 101 102 m, err := c.roundTrip(ctx, req.Addr, &offsetcommit.Request{ 103 GroupID: req.GroupID, 104 GenerationID: int32(req.GenerationID), 105 MemberID: req.MemberID, 106 GroupInstanceID: req.InstanceID, 107 Topics: topics, 108 // Hardcoded retention; this field existed between v2 and v4 of the 109 // OffsetCommit API, we would have to figure out a way to give the 110 // client control over the API version being used to support configuring 111 // it in the request object. 112 RetentionTimeMs: int64((24 * time.Hour) / time.Millisecond), 113 }) 114 if err != nil { 115 return nil, fmt.Errorf("kafka.(*Client).OffsetCommit: %w", err) 116 } 117 r := m.(*offsetcommit.Response) 118 119 res := &OffsetCommitResponse{ 120 Throttle: makeDuration(r.ThrottleTimeMs), 121 Topics: make(map[string][]OffsetCommitPartition, len(r.Topics)), 122 } 123 124 for _, topic := range r.Topics { 125 partitions := make([]OffsetCommitPartition, len(topic.Partitions)) 126 127 for i, p := range topic.Partitions { 128 partitions[i] = OffsetCommitPartition{ 129 Partition: int(p.PartitionIndex), 130 Error: makeError(p.ErrorCode, ""), 131 } 132 } 133 134 res.Topics[topic.Name] = partitions 135 } 136 137 return res, nil 138 } 139 140 type offsetCommitRequestV2Partition struct { 141 // Partition ID 142 Partition int32 143 144 // Offset to be committed 145 Offset int64 146 147 // Metadata holds any associated metadata the client wants to keep 148 Metadata string 149 } 150 151 func (t offsetCommitRequestV2Partition) size() int32 { 152 return sizeofInt32(t.Partition) + 153 sizeofInt64(t.Offset) + 154 sizeofString(t.Metadata) 155 } 156 157 func (t offsetCommitRequestV2Partition) writeTo(wb *writeBuffer) { 158 wb.writeInt32(t.Partition) 159 wb.writeInt64(t.Offset) 160 wb.writeString(t.Metadata) 161 } 162 163 type offsetCommitRequestV2Topic struct { 164 // Topic name 165 Topic string 166 167 // Partitions to commit offsets 168 Partitions []offsetCommitRequestV2Partition 169 } 170 171 func (t offsetCommitRequestV2Topic) size() int32 { 172 return sizeofString(t.Topic) + 173 sizeofArray(len(t.Partitions), func(i int) int32 { return t.Partitions[i].size() }) 174 } 175 176 func (t offsetCommitRequestV2Topic) writeTo(wb *writeBuffer) { 177 wb.writeString(t.Topic) 178 wb.writeArray(len(t.Partitions), func(i int) { t.Partitions[i].writeTo(wb) }) 179 } 180 181 type offsetCommitRequestV2 struct { 182 // GroupID holds the unique group identifier 183 GroupID string 184 185 // GenerationID holds the generation of the group. 186 GenerationID int32 187 188 // MemberID assigned by the group coordinator 189 MemberID string 190 191 // RetentionTime holds the time period in ms to retain the offset. 192 RetentionTime int64 193 194 // Topics to commit offsets 195 Topics []offsetCommitRequestV2Topic 196 } 197 198 func (t offsetCommitRequestV2) size() int32 { 199 return sizeofString(t.GroupID) + 200 sizeofInt32(t.GenerationID) + 201 sizeofString(t.MemberID) + 202 sizeofInt64(t.RetentionTime) + 203 sizeofArray(len(t.Topics), func(i int) int32 { return t.Topics[i].size() }) 204 } 205 206 func (t offsetCommitRequestV2) writeTo(wb *writeBuffer) { 207 wb.writeString(t.GroupID) 208 wb.writeInt32(t.GenerationID) 209 wb.writeString(t.MemberID) 210 wb.writeInt64(t.RetentionTime) 211 wb.writeArray(len(t.Topics), func(i int) { t.Topics[i].writeTo(wb) }) 212 } 213 214 type offsetCommitResponseV2PartitionResponse struct { 215 Partition int32 216 217 // ErrorCode holds response error code 218 ErrorCode int16 219 } 220 221 func (t offsetCommitResponseV2PartitionResponse) size() int32 { 222 return sizeofInt32(t.Partition) + 223 sizeofInt16(t.ErrorCode) 224 } 225 226 func (t offsetCommitResponseV2PartitionResponse) writeTo(wb *writeBuffer) { 227 wb.writeInt32(t.Partition) 228 wb.writeInt16(t.ErrorCode) 229 } 230 231 func (t *offsetCommitResponseV2PartitionResponse) readFrom(r *bufio.Reader, size int) (remain int, err error) { 232 if remain, err = readInt32(r, size, &t.Partition); err != nil { 233 return 234 } 235 if remain, err = readInt16(r, remain, &t.ErrorCode); err != nil { 236 return 237 } 238 return 239 } 240 241 type offsetCommitResponseV2Response struct { 242 Topic string 243 PartitionResponses []offsetCommitResponseV2PartitionResponse 244 } 245 246 func (t offsetCommitResponseV2Response) size() int32 { 247 return sizeofString(t.Topic) + 248 sizeofArray(len(t.PartitionResponses), func(i int) int32 { return t.PartitionResponses[i].size() }) 249 } 250 251 func (t offsetCommitResponseV2Response) writeTo(wb *writeBuffer) { 252 wb.writeString(t.Topic) 253 wb.writeArray(len(t.PartitionResponses), func(i int) { t.PartitionResponses[i].writeTo(wb) }) 254 } 255 256 func (t *offsetCommitResponseV2Response) readFrom(r *bufio.Reader, size int) (remain int, err error) { 257 if remain, err = readString(r, size, &t.Topic); err != nil { 258 return 259 } 260 261 fn := func(r *bufio.Reader, withSize int) (fnRemain int, fnErr error) { 262 item := offsetCommitResponseV2PartitionResponse{} 263 if fnRemain, fnErr = (&item).readFrom(r, withSize); fnErr != nil { 264 return 265 } 266 t.PartitionResponses = append(t.PartitionResponses, item) 267 return 268 } 269 if remain, err = readArrayWith(r, remain, fn); err != nil { 270 return 271 } 272 273 return 274 } 275 276 type offsetCommitResponseV2 struct { 277 Responses []offsetCommitResponseV2Response 278 } 279 280 func (t offsetCommitResponseV2) size() int32 { 281 return sizeofArray(len(t.Responses), func(i int) int32 { return t.Responses[i].size() }) 282 } 283 284 func (t offsetCommitResponseV2) writeTo(wb *writeBuffer) { 285 wb.writeArray(len(t.Responses), func(i int) { t.Responses[i].writeTo(wb) }) 286 } 287 288 func (t *offsetCommitResponseV2) readFrom(r *bufio.Reader, size int) (remain int, err error) { 289 fn := func(r *bufio.Reader, withSize int) (fnRemain int, fnErr error) { 290 item := offsetCommitResponseV2Response{} 291 if fnRemain, fnErr = (&item).readFrom(r, withSize); fnErr != nil { 292 return 293 } 294 t.Responses = append(t.Responses, item) 295 return 296 } 297 if remain, err = readArrayWith(r, size, fn); err != nil { 298 return 299 } 300 301 return 302 }