package plugin import ( "context" "fmt" "github.com/seaweedfs/seaweedfs/weed/pb/plugin_pb" "google.golang.org/protobuf/types/known/timestamppb" ) // maxPreviewRows caps what admin will ask for. A preview is a look at the // object, not an export, and the rows cross the control stream. const maxPreviewRows = 200 // RequestObjectPreview asks the worker that last described an object for sample // rows of it, and waits for the answer. // // The worker is chosen from the observation store rather than by job type: the // one that last looked at this object is the one that can read it, and it says // so by having reported. That also means a preview is only available once // detection has run, which the caller should surface rather than hide. func (r *Plugin) RequestObjectPreview(ctx context.Context, objectID []string, format string, rowLimit int) (*plugin_pb.ObjectPreviewResponse, error) { if len(objectID) == 0 { return nil, fmt.Errorf("preview needs an object id") } // The conversion happens only inside the bound check, so what reaches the // wire cannot depend on int being wider than int32 here. requestedRows := int32(maxPreviewRows) if rowLimit >= 1 && rowLimit <= maxPreviewRows { requestedRows = int32(rowLimit) } observed, ok := r.observations.GetFormat(objectID, format) if !ok { return nil, fmt.Errorf("no worker has described this object as %s yet", format) } if _, connected := r.registry.Get(observed.WorkerID); !connected { return nil, fmt.Errorf("worker %s is not connected", observed.WorkerID) } requestID, err := newRequestID("preview") if err != nil { return nil, err } responseCh := make(chan *plugin_pb.ObjectPreviewResponse, 1) r.pendingPreviewMu.Lock() r.pendingPreview[requestID] = responseCh r.pendingPreviewMu.Unlock() defer func() { r.pendingPreviewMu.Lock() delete(r.pendingPreview, requestID) r.pendingPreviewMu.Unlock() }() request := &plugin_pb.AdminToWorkerMessage{ RequestId: requestID, SentAt: timestamppb.Now(), Body: &plugin_pb.AdminToWorkerMessage_RequestObjectPreview{ RequestObjectPreview: &plugin_pb.RequestObjectPreview{ ObjectId: objectID, Format: format, RowLimit: requestedRows, }, }, } if err := r.sendToWorker(observed.WorkerID, request); err != nil { return nil, err } select { case <-ctx.Done(): return nil, ctx.Err() case response, ok := <-responseCh: if !ok { return nil, fmt.Errorf("preview request %s interrupted", requestID) } if response == nil { return nil, fmt.Errorf("preview request %s returned nothing", requestID) } if !response.Success { return nil, fmt.Errorf("worker %s could not preview the object: %s", observed.WorkerID, response.ErrorMessage) } return response, nil } } // handleObjectPreviewResponse routes one reply back to whoever is waiting. func (r *Plugin) handleObjectPreviewResponse(response *plugin_pb.ObjectPreviewResponse) { if response == nil { return } // Held across the send: Shutdown closes these channels under the same lock, // and a send that raced it would panic on a closed channel. r.pendingPreviewMu.Lock() defer r.pendingPreviewMu.Unlock() ch := r.pendingPreview[response.RequestId] if ch == nil { return } select { case ch <- response: default: } }