diff --git a/api-remove.go b/api-remove.go
index f14b2eb7f2..824d6ee87f 100644
--- a/api-remove.go
+++ b/api-remove.go
@@ -129,10 +129,8 @@ func processRemoveMultiObjectsResponse(body io.Reader, objects []string, errorCh
}
}
-// RemoveObjects remove multiples objects from a bucket.
-// The list of objects to remove are received from objectsCh.
-// Remove failures are sent back via error channel.
-func (c Client) RemoveObjects(bucketName string, objectsCh <-chan string) <-chan RemoveObjectError {
+// RemoveObjectsWithContext - Identical to RemoveObjects call, but accepts context to facilitate request cancellation.
+func (c Client) RemoveObjectsWithContext(ctx context.Context, bucketName string, objectsCh <-chan string) <-chan RemoveObjectError {
errorCh := make(chan RemoveObjectError, 1)
// Validate if bucket name is valid.
@@ -189,7 +187,7 @@ func (c Client) RemoveObjects(bucketName string, objectsCh <-chan string) <-chan
// Generate remove multi objects XML request
removeBytes := generateRemoveMultiObjectsRequest(batch)
// Execute GET on bucket to list objects.
- resp, err := c.executeMethod(context.Background(), "POST", requestMetadata{
+ resp, err := c.executeMethod(ctx, "POST", requestMetadata{
bucketName: bucketName,
queryValues: urlValues,
contentBody: bytes.NewReader(removeBytes),
@@ -213,6 +211,13 @@ func (c Client) RemoveObjects(bucketName string, objectsCh <-chan string) <-chan
return errorCh
}
+// RemoveObjects removes multiple objects from a bucket.
+// The list of objects to remove are received from objectsCh.
+// Remove failures are sent back via error channel.
+func (c Client) RemoveObjects(bucketName string, objectsCh <-chan string) <-chan RemoveObjectError {
+ return c.RemoveObjectsWithContext(context.Background(), bucketName, objectsCh)
+}
+
// RemoveIncompleteUpload aborts an partially uploaded object.
func (c Client) RemoveIncompleteUpload(bucketName, objectName string) error {
// Input validation.
diff --git a/docs/API.md b/docs/API.md
index 09674c0ca2..66f65061d3 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -68,6 +68,7 @@ func main() {
| | [`GetObjectWithContext`](#GetObjectWithContext) | | | |
| | [`FPutObjectWithContext`](#FPutObjectWithContext) | | | |
| | [`FGetObjectWithContext`](#FGetObjectWithContext) | | | |
+| | [`RemoveObjectsWithContext`](#RemoveObjectsWithContext) | | | |
## 1. Constructor
@@ -1062,6 +1063,47 @@ for rErr := range minioClient.RemoveObjects("mybucket", objectsCh) {
}
```
+
+### RemoveObjectsWithContext(ctx context.Context, bucketName string, objectsCh chan string) (errorCh <-chan RemoveObjectError)
+*Identical to RemoveObjects operation, but accepts a context for request cancellation.*
+
+Parameters
+
+|Param |Type |Description |
+|:---|:---| :---|
+|`ctx` | _context.Context_ |Request context |
+|`bucketName` | _string_ |Name of the bucket |
+|`objectsCh` | _chan string_ | Channel of objects to be removed |
+
+
+__Return Values__
+
+|Param |Type |Description |
+|:---|:---| :---|
+|`errorCh` | _<-chan minio.RemoveObjectError_ | Receive-only channel of errors observed during deletion. |
+
+```go
+objectsCh := make(chan string)
+ctx, cancel := context.WithTimeout(context.Background(), 100 * time.Second)
+defer cancel()
+
+// Send object names that are needed to be removed to objectsCh
+go func() {
+ defer close(objectsCh)
+ // List all objects from a bucket-name with a matching prefix.
+ for object := range minioClient.ListObjects("my-bucketname", "my-prefixname", true, nil) {
+ if object.Err != nil {
+ log.Fatalln(object.Err)
+ }
+ objectsCh <- object.Key
+ }
+}()
+
+for rErr := range minioClient.RemoveObjects(ctx, "my-bucketname", objectsCh) {
+ fmt.Println("Error detected during deletion: ", rErr)
+}
+```
+
### RemoveIncompleteUpload(bucketName, objectName string) error
Removes a partially uploaded object.
diff --git a/functional_tests.go b/functional_tests.go
index 78f9509e6e..36486b004e 100644
--- a/functional_tests.go
+++ b/functional_tests.go
@@ -1101,6 +1101,101 @@ func testGetObjectClosedTwice() {
successLogger(testName, function, args, startTime).Info()
}
+// Test RemoveObjectsWithContext request context cancels after timeout
+func testRemoveObjectsWithContext() {
+ // Initialize logging params.
+ startTime := time.Now()
+ testName := getFuncName()
+ function := "RemoveObjectsWithContext(ctx, bucketName, objectsCh)"
+ args := map[string]interface{}{
+ "bucketName": "",
+ }
+
+ // Seed random based on current tie.
+ rand.Seed(time.Now().Unix())
+
+ // Instantiate new minio client.
+ c, err := minio.New(
+ os.Getenv(serverEndpoint),
+ os.Getenv(accessKey),
+ os.Getenv(secretKey),
+ mustParseBool(os.Getenv(enableHTTPS)),
+ )
+ if err != nil {
+ logError(testName, function, args, startTime, "", "Minio client object creation failed", err)
+ return
+ }
+
+ // Set user agent.
+ c.SetAppInfo("Minio-go-FunctionalTest", "0.1.0")
+ // Enable tracing, write to stdout.
+ // c.TraceOn(os.Stderr)
+
+ // Generate a new random bucket name.
+ bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-")
+ args["bucketName"] = bucketName
+
+ // Make a new bucket.
+ err = c.MakeBucket(bucketName, "us-east-1")
+ if err != nil {
+ logError(testName, function, args, startTime, "", "MakeBucket failed", err)
+ }
+
+ // Generate put data.
+ r := bytes.NewReader(bytes.Repeat([]byte("a"), 8))
+
+ // Multi remove of 20 objects.
+ nrObjects := 20
+ objectsCh := make(chan string)
+ go func() {
+ defer close(objectsCh)
+ for i := 0; i < nrObjects; i++ {
+ objectName := "sample" + strconv.Itoa(i) + ".txt"
+ _, err = c.PutObject(bucketName, objectName, r, 8, minio.PutObjectOptions{ContentType: "application/octet-stream"})
+ if err != nil {
+ logError(testName, function, args, startTime, "", "PutObject failed", err)
+ continue
+ }
+ objectsCh <- objectName
+ }
+ }()
+ // Set context to cancel in 1 nanosecond.
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
+ args["ctx"] = ctx
+ defer cancel()
+
+ // Call RemoveObjectsWithContext API with short timeout.
+ errorCh := c.RemoveObjectsWithContext(ctx, bucketName, objectsCh)
+ // Check for error.
+ select {
+ case r := <-errorCh:
+ if r.Err == nil {
+ logError(testName, function, args, startTime, "", "RemoveObjectsWithContext should fail on short timeout", err)
+ return
+ }
+ }
+ // Set context with longer timeout.
+ ctx, cancel = context.WithTimeout(context.Background(), 1*time.Hour)
+ args["ctx"] = ctx
+ defer cancel()
+ // Perform RemoveObjectsWithContext with the longer timeout. Expect the removals to succeed.
+ errorCh = c.RemoveObjectsWithContext(ctx, bucketName, objectsCh)
+ select {
+ case r, more := <-errorCh:
+ if more || r.Err != nil {
+ logError(testName, function, args, startTime, "", "Unexpected error", r.Err)
+ return
+ }
+ }
+
+ // Delete all objects and buckets.
+ if err = cleanupBucket(bucketName, c); err != nil {
+ logError(testName, function, args, startTime, "", "Cleanup failed", err)
+ return
+ }
+ successLogger(testName, function, args, startTime).Info()
+}
+
// Test removing multiple objects with Remove API
func testRemoveMultipleObjects() {
// initialize logging params